2026-09-13 01:01:48 +02:00
2026-09-13 01:01:48 +02:00
2026-09-13 00:50:15 +02:00
2026-09-12 23:26:07 +02:00
2026-09-12 23:26:07 +02:00
2026-09-12 23:48:03 +02:00
2026-09-13 01:01:48 +02:00
2026-09-13 01:01:48 +02:00
2026-09-12 23:26:07 +02:00
2026-09-12 23:26:07 +02:00

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 http://localhost:8080, 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 <token>' \
     -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.

S
Description
A small self-hosted file drop.
https://send.uncensored.hu/
Readme
480 KiB
Languages
Go 89.3%
HTML 5.2%
CSS 3.3%
JavaScript 2.2%