Files
scratchbox/README.md
T
s1d3sw1ped ad4355df17
CI / Go Tests (push) Successful in 15s
CI / Build (push) Successful in 9s
Format / gofmt (push) Successful in 7s
Release Artifacts / Validate release tag (push) Successful in 2s
Release Artifacts / Build and release executables (push) Successful in 15s
Release Artifacts / Build and release Docker image (push) Successful in 28s
Heavy security and file splitting
2026-06-01 20:15:28 -05:00

13 KiB

Scratchbox

Scratchbox is a minimal self-hosted scratch service written in Go. It is intentionally unauthenticated and optimized for temporary sharing of text or small files with an expiration time.

For multi-file sharing, bundle files into an archive (for example .zip or .tar.gz) before upload. Scratch content is gzip-compressed and AES-encrypted on disk transparently; API/UI reads return original uncompressed content.

Quick Start

  1. Generate defaults directly:
    • go run ./cmd/scratchbox generate-config > config.yaml
  2. Adjust settings for your environment.
  3. Start the server:
    • go run ./cmd/scratchbox server --config config.yaml
  4. Open:
    • http://127.0.0.1:8080/

Without --config, built-in defaults are used.

If you modify the web UI source (web/ui), rebuild frontend assets with:

  • make build

Docker

Build the container image:

  • make docker
  • Default image tag: scratchbox:dev

Optionally set a custom image name/tag:

  • make docker DOCKER_IMAGE=scratchbox:latest

Run with persistent data and env-based config:

  • mkdir -p ./docker-data
  • cp .env.example .env
  • docker run --rm -p 8080:8080 --env-file .env -v "$(pwd)/docker-data:/data" scratchbox:dev
  • Without .env, the image still starts with default config and uses SCRATCHBOX_STORAGE_DATA_DIR=/data.
  • The image always forces SCRATCHBOX_STORAGE_DATA_DIR=/data.
  • The image always forces SCRATCHBOX_SERVER_LISTEN_ADDR=:8080.
  • The image always forces SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH to empty (stdout-only access logs).

Use the included Compose example:

  • cp docker-compose.example.yml docker-compose.yml
  • cp .env.example .env
  • mkdir -p ./docker-data
  • docker compose up -d

Inside the container, the default command is:

  • scratchbox server --env
  • Custom container command/args are not supported; configure behavior via SCRATCHBOX_* env vars.
  • The image forces SCRATCHBOX_STORAGE_DATA_DIR=/data.
  • The image forces SCRATCHBOX_SERVER_LISTEN_ADDR=:8080.
  • The image forces SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH to empty.

Storage encryption key behavior:

  • The key file is always stored at storage.data_dir/metadata.key (next to metadata).
  • Scratchbox only auto-generates this key when storage.data_dir has no existing files.
  • If the key is missing and files already exist in storage.data_dir, startup fails to prevent corruption.
  • On subsequent startups, Scratchbox reuses the same key file from that data directory.
  • Keep storage.data_dir persistent; losing or replacing metadata.key makes existing scratches unreadable.

Environment variables supported by scratchbox server --env (note: when --env is used, the variables and their values are printed to stdout on startup for diagnostics, with values of IP allow/trusted lists redacted):

  • SCRATCHBOX_SERVER_LISTEN_ADDR
  • SCRATCHBOX_SERVER_READ_HEADER_TIMEOUT
  • SCRATCHBOX_SERVER_READ_TIMEOUT
  • SCRATCHBOX_SERVER_WRITE_TIMEOUT
  • SCRATCHBOX_SERVER_IDLE_TIMEOUT
  • SCRATCHBOX_SERVER_SHUTDOWN_TIMEOUT
  • SCRATCHBOX_SERVER_MAX_HEADER_BYTES
  • SCRATCHBOX_SERVER_ACCESS_LOG_ENABLED
  • SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH (forced empty by Docker image)
  • SCRATCHBOX_SERVER_ACCESS_LOG_MAX_SIZE
  • SCRATCHBOX_SERVER_ACCESS_LOG_MAX_BACKUPS
  • SCRATCHBOX_LIMITS_MAX_UPLOAD_SIZE
  • SCRATCHBOX_LIMITS_DEFAULT_TTL
  • SCRATCHBOX_LIMITS_RAW_CACHE_MAX_SIZE
  • SCRATCHBOX_LIMITS_RAW_CACHE_MAX_ENTRIES
  • SCRATCHBOX_STORAGE_DATA_DIR
  • SCRATCHBOX_STORAGE_CLEANUP_INTERVAL
  • SCRATCHBOX_SECURITY_ALLOWED_IPS (comma-separated)
  • SCRATCHBOX_SECURITY_TRUST_PROXY_HEADERS
  • SCRATCHBOX_SECURITY_TRUSTED_PROXY_IPS (comma-separated)
  • SCRATCHBOX_SECURITY_RATE_LIMIT_UI_ENABLED
  • SCRATCHBOX_SECURITY_RATE_LIMIT_UI_REQUESTS_PER_MINUTE
  • SCRATCHBOX_SECURITY_RATE_LIMIT_UI_BURST
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_ENABLED
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_REQUESTS_PER_MINUTE
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_BURST
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_ENABLED
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_REQUESTS_PER_MINUTE
  • SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_BURST
  • SCRATCHBOX_SECURITY_HSTS_ENABLED

Example .env for Docker:

# server
SCRATCHBOX_SERVER_LISTEN_ADDR=:8080
SCRATCHBOX_SERVER_READ_HEADER_TIMEOUT=5s
SCRATCHBOX_SERVER_READ_TIMEOUT=30s
SCRATCHBOX_SERVER_WRITE_TIMEOUT=30s
SCRATCHBOX_SERVER_IDLE_TIMEOUT=120s
SCRATCHBOX_SERVER_SHUTDOWN_TIMEOUT=10s
SCRATCHBOX_SERVER_MAX_HEADER_BYTES=1048576
SCRATCHBOX_SERVER_ACCESS_LOG_ENABLED=true

# limits
SCRATCHBOX_LIMITS_MAX_UPLOAD_SIZE=100MiB
SCRATCHBOX_LIMITS_DEFAULT_TTL=15m
SCRATCHBOX_LIMITS_RAW_CACHE_MAX_SIZE=200MiB
SCRATCHBOX_LIMITS_RAW_CACHE_MAX_ENTRIES=32

# storage
SCRATCHBOX_STORAGE_DATA_DIR=/data
SCRATCHBOX_STORAGE_CLEANUP_INTERVAL=1m

# security
SCRATCHBOX_SECURITY_ALLOWED_IPS=127.0.0.1
SCRATCHBOX_SECURITY_TRUST_PROXY_HEADERS=false
SCRATCHBOX_SECURITY_TRUSTED_PROXY_IPS=
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_ENABLED=false
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_REQUESTS_PER_MINUTE=360
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_BURST=120
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_ENABLED=false
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_REQUESTS_PER_MINUTE=300
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_BURST=100
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_ENABLED=true
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_REQUESTS_PER_MINUTE=30
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_BURST=10
SCRATCHBOX_SECURITY_HSTS_ENABLED=true

Release Artifacts

Tagged releases provide .tar.gz archives with prebuilt Linux binaries.

Accepted release tag formats:

  • 1.2.3 (stable release)
  • 1.2.3-alpha1, 1.2.3-beta1, 1.2.3-rc1 (prerelease)

Variant guide:

Variant Recommended for Notes
default Most Linux distributions (Ubuntu, Debian, Fedora, Arch, etc.) Smaller, dynamically linked Linux build
static Any Linux distro (including Alpine/musl) Statically linked for maximum Linux compatibility

To unpack an artifact:

  • tar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz

To unpack into a specific directory:

  • mkdir -p ./scratchbox-release
  • tar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz -C ./scratchbox-release

To run from an unpacked artifact:

  • ./scratchbox-<os>-<arch>-<variant> server --config config.yaml

If you do not have a config file yet, generate one first:

  • go run ./cmd/scratchbox generate-config > config.yaml

Configuration

All configuration is YAML.

server

  • listen_addr: address for the HTTP server (required).
    • Default: :8080
  • read_header_timeout: maximum time for reading request headers.
    • Must be > 0
    • Default: 5s
  • read_timeout: maximum time for reading the full request.
    • Must be > 0
    • Default: 30s
  • write_timeout: maximum time allowed for writing the response.
    • Must be > 0
    • Default: 30s
  • idle_timeout: maximum keep-alive idle time between requests.
    • Must be > 0
    • Default: 120s
  • shutdown_timeout: graceful shutdown timeout for in-flight requests on SIGTERM/INT.
    • Must be > 0
    • Default: 10s
  • max_header_bytes: max HTTP header size in bytes.
    • Must be > 0
    • Default: 1048576 (1 MiB)
  • access_log: structured request logs.
    • enabled: enable HTTP access logs
      • Default: true
    • file_path: optional log file path
      • When set, logs are tee'd to both stdout and the file
    • max_size: rotation threshold for file_path
      • Supported units: B, KB, MB, GB, KiB, MiB, GiB
      • Default: 100MiB
    • max_backups: number of rotated files to retain
      • Default: 5

limits

  • max_upload_size: max request body size for uploads.
    • Supported units: B, KB, MB, GB, KiB, MiB, GiB (case-insensitive)
    • If no unit is provided, value is interpreted as bytes
    • Examples: 5MB, 100MiB, 1GiB, 1048576
    • Default: 100MiB
  • default_ttl: expiration applied to new scratches.
    • Must be > 0 and <= 24h
    • Default: 15m
  • raw_cache_max_size: max total decompressed bytes cached in memory for /api/raw range requests.
    • Supported units: B, KB, MB, GB, KiB, MiB, GiB (case-insensitive)
    • Must be > 0
    • Default: 200MiB
  • raw_cache_max_entries: max number of decompressed /api/raw entries kept in memory.
    • Must be > 0
    • Used together with raw_cache_max_size as a secondary cap
    • Default: 32

storage

  • data_dir: directory for scratch files and metadata index.
    • Default: ./data
  • cleanup_interval: background interval for deleting expired content.
    • Must be at least 10s
    • Default: 1m
  • Encryption key file is managed automatically at storage.data_dir/metadata.key.
    • Auto-generated only when storage.data_dir has no existing files
    • If missing while files exist in storage.data_dir, startup fails
    • Reused on subsequent startups for the same data directory
    • Changing/removing this file makes existing scratch files unreadable

security

  • allowed_ips: optional list of IPs and CIDRs allowed to create scratches.
    • Applies to write route only: POST /api/scratch
    • Default includes 127.0.0.1 (localhost-only write access)
    • Add trusted IPs/CIDRs for LAN/proxy clients (for example 192.168.1.0/24)
    • Empty list means no allowlist restriction
  • trust_proxy_headers: if true, client IP extraction honors X-Forwarded-For then X-Real-IP.
    • Only used when source IP matches trusted_proxy_ips.
    • Keep false unless behind a trusted reverse proxy that sets these headers.
  • trusted_proxy_ips: list of reverse-proxy IPs/CIDRs permitted to supply forwarded headers.
    • Required when trust_proxy_headers: true
    • Must be the immediate reverse proxies only (e.g. the nginx/caddy in front of this service); never use 0.0.0.0/0 or broad ranges unless you fully control the entire path.
    • Examples: 127.0.0.1, 10.0.0.0/8
  • rate_limit_ui: per-IP token-bucket for UI routes (GET /, GET /u, GET /s/{id}).
    • enabled default: false
    • requests_per_minute must be > 0 (default 360)
    • burst must be > 0 (default 120)
  • rate_limit_api_read: per-IP token-bucket for API read routes (GET /api/config, GET /api/scratch/{id}, GET /api/raw/{id}).
    • enabled default: false
    • requests_per_minute must be > 0 (default 300)
    • burst must be > 0 (default 100)
  • rate_limit_api_write: per-IP token-bucket for API write route (POST /api/scratch).
    • enabled default: true
    • requests_per_minute must be > 0 (default 30)
    • burst must be > 0 (default 10)
  • hsts_enabled: enable HSTS header (default: true; for public proxy TLS setups; set false for local HTTP-only per LAN notes).

Security Posture

This service is intentionally unauthenticated. Anyone with network access can read scratches, and (unless restricted) create scratches.

  • No authentication, user accounts, or moderation controls.
  • No malware scanning.
  • Abuse controls are limited to upload size limits, TTL expiration, optional IP allowlisting, and write-route rate limiting.
  • Treat this as a convenience utility, not a hardened internet-facing platform.

LAN Deployment Notes

For home/lab/LAN-only use:

  • Bind to a private address, for example 127.0.0.1:8080 (single host) or 192.168.x.x:8080.
  • Use allowed_ips to restrict write access to trusted subnets.
  • Keep max_upload_size and default_ttl small.
  • Keep trust_proxy_headers disabled unless using a trusted reverse proxy.

Public Deployment Notes

If exposed beyond a trusted LAN, place a reverse proxy in front (Nginx, Caddy, Traefik, etc.) and add external controls:

  • TLS termination and strict transport settings.
  • Additional request filtering/WAF controls.
  • Stronger rate limits at the edge.
  • Optional network-level restrictions to write route.
  • Monitoring and log retention suitable for abuse triage.

Recommended baseline: run behind a reverse proxy, keep low TTL and size limits, and use allowed_ips for write access whenever possible.

Storage Model

Scratchbox storage is designed for a single process instance per storage.data_dir.

  • Do not run multiple scratchbox processes against the same data directory.
  • Metadata/index writes are process-local and are not coordinated with cross-process locking.
  • Scratch payload files and metadata index are encrypted at rest.
  • Public scratch IDs are short random aliases; stored blobs still use SHA-256-derived internal blob IDs on disk.

Operational internals (not tunable; see source consts):

  • Public IDs: 12 alphanum chars (crypto/rand), up to 8 attempts to reserve unique.
  • Encrypted chunks: 64 KiB plaintext (SBX1 magic + per-chunk nonce prefix+counter for AES-256-GCM).
  • Index: full random nonce per persist (SMD1).
  • Background expiry + startup reconcile walk the in-memory map under exclusive lock (plus optional persist); practical cardinality limited by default 15m TTL + 100MiB uploads (low hundreds of live entries expected).
  • Raw /api/raw cache: small LRU (default 32 entries, total bytes cap) with coalescing; serves decompressed content.