# Uncensored Send A small self-hosted file drop. Uploads land in a flat data directory, expire on their own, and are served back as inert attachments. Built for sharing large binaries with friends — a 400 MB Godot export is a normal day here — without a `tmp/` folder that grows forever. - One static Go binary, standard library only. No database, no CSS or JavaScript build step, nothing to install. - Anyone who can reach the page may upload, within a size cap and a lifetime. - Named tokens raise those limits, unlock custom URLs, and can grant admin. - Everything expires unless a token says otherwise. ## Quickstart ``` go build -o send . ./send token add me --token - --vanity --admin # type a passphrase, or omit ./send --port 8080 # --data defaults to ./data ``` Open , click **Log in**, paste the token. That's it. `--token -` reads a passphrase you choose from standard input; leave the flag off and a random one is generated and printed once. Either way `--data` must name the *same directory* the server runs with, or the server will never see the token. Uploading from a script: ``` curl --data-binary @MyGame.zip \ -H 'Content-Disposition: attachment; filename="MyGame.zip"' \ -H 'Authorization: Bearer ' \ -H 'Vanity: my-game' -H 'Expiry: 7d' \ http://localhost:8080/api/upload ``` The reply carries the download URL and a delete token. Use a generated token for scripts — a chosen passphrase is verified with a deliberately slow derivation, which is wasted on every request a script makes. In production, put it behind a reverse proxy that terminates TLS, set `--public-url`, and make sure the proxy neither buffers request bodies nor imposes its own upload limit. ## Options **Read `./send --help` rather than this file.** It lists every option with its default and its environment variable, and unlike a README it cannot drift out of date. `./send token --help` does the same for credentials. Options take **one hyphen with a single letter** and **two with a full word**: `-s 4GiB` and `--max-size=4GiB` are the same option; `-max-size` is an error. Every option also reads from `SEND_`-prefixed environment variables. ## Development ``` go test ./... go vet ./... ``` Layout: `main.go` and `token.go` are the CLI; `internal/config` parses options; `internal/store` is the object store; `internal/auth` is credentials; `internal/server` is the HTTP surface; `web/` holds the templates and assets, embedded at build time. A few invariants worth knowing before changing anything: - **Upload size is never taken from `Content-Length`.** It is enforced on bytes actually written, and the transfer is cut off the moment it is exceeded. - **Nothing served from `/d/` may execute.** Always `application/octet-stream`, always an attachment, always `nosniff` and `default-src 'none'; sandbox`. - **Filenames are metadata, never paths.** Every path is built from a validated ID. `store.CleanID` is the only function allowed to turn input into a path element, and object files are opened through an `os.Root`. - **Expiry is checked on every read**, not only by the sweeper, and a missing file and an expired one answer identically. - **Uploads are published atomically**: the blob is fsynced and renamed into place before the metadata that advertises it, also by rename. A crash leaves something invisible rather than something broken. - **Generated tokens are hashed with SHA-256; chosen ones get PBKDF2** with their own salt. A 256-bit random value has nothing to crack, while a passphrase is guessable and probably reused elsewhere. The derivation is memoised per process and rate limited, so it cannot be used as an amplifier. - **The session cookie is `HttpOnly` and `SameSite=Strict`**, and every `POST` must be same-origin — `SameSite` does not cover logging in, which needs no cookie to submit. - **The app pages and the download responses have different CSP policies.** The app one must permit `connect-src` or the upload script is blocked; the download one must grant nothing at all. Both are pinned by tests. The tests cover the places where a mistake is expensive: size limits against a body with no declared length, vanity collisions, expiry on read, traversal and reserved names, `Content-Disposition` for hostile filenames, delete authorisation, symlink escapes, crash debris, CSRF, and credential handling.