sndr
A command-line client for the Send end-to-end-encrypted file-sharing protocol. Upload a file, hand someone a one-shot link, walk away. The server stores ciphertext, the recipient holds the only key, the share expires on its own.
That URL is everything: file ID in the path, decryption key after the
#. Anyone who gets it can fetch and decrypt — and only them, because
the server never sees the key.
Why use this
- Send a file once, securely — the alternative is "DM you a Google
Drive link" (the operator holds your file forever) or "SFTP you onto
my box" (you need an account and a network path).
sndris one command, no signup, no persistent footprint. - Encrypted before it leaves your machine — AES-128-GCM with a random 128-bit key. The server stores opaque ciphertext.
- One-shot by default — 1 download, 24-hour expiry. Configurable up to 20 downloads / 7 days on most instances.
- Optional password on top of the URL secret, for cases where the link itself might leak.
- Server-shipped JavaScript is out of the loop —
sndrships its own implementation of the protocol, so a compromised server cannot swap out the crypto layer the way it could for a browser client. This is the real reason to use a CLI over the web UI for sensitive transfers.
It's not the right tool for everything. If you need persistent hosting, multi-recipient access control, or files larger than a few hundred MB on a public instance, use something else.
Install
sndr is a young fork and is not yet packaged on crates.io or any
distro. Install from source:
Requires Rust 1.63 or newer. On Linux, you also want ca-certificates
(usually already installed), and xclip or xsel if you want
--copy to work. No system OpenSSL needed — the default crypto
backend is pure Rust.
For a hands-on build (cloning, debug build, testing locally), see Build below.
Usage
Upload
# The simple case
# 5 downloads, 1 hour expiry, password-protect, copy URL to clipboard
# Upload a directory (auto-tars it)
# Read from stdin
|
# Different host
Receive
# Download to current directory, picking the original filename
# Download to a specific path
# Inspect without downloading (does not consume a download slot)
Manage your shares
sndr keeps a local index of shares you've uploaded, so you can list,
re-share, or revoke them without re-pasting URLs.
The history file lives at ~/.cache/sndr/history.toml and contains
owner tokens, so it's stored with 0600 perms. Use --incognito
(or SNDR_INCOGNITO=1) to skip recording anything locally.
Configuration
Every flag has an environment variable equivalent. Set what you use most often in your shell rc.
| Variable | Flag | Default |
|---|---|---|
SNDR_HOST |
--host <URL> |
https://snd.dx.pe/ |
SNDR_HISTORY |
--history <FILE> |
~/.cache/sndr/history.toml |
SNDR_EXPIRY_TIME |
--expiry-time <SECONDS> |
24 h |
SNDR_DOWNLOAD_LIMIT |
--download-limit <N> |
1 |
SNDR_TIMEOUT |
--timeout <SECONDS> |
30 s |
SNDR_TRANSFER_TIMEOUT |
--transfer-timeout <SECONDS> |
24 h |
SNDR_BASIC_AUTH |
--basic-auth <USER:PASSWORD> |
none |
SNDR_API |
--api <VERSION> |
autodetect |
Flag-style env vars (presence enables; the value is ignored):
SNDR_FORCE SNDR_NO_INTERACT SNDR_YES SNDR_INCOGNITO
SNDR_OPEN SNDR_ARCHIVE SNDR_EXTRACT SNDR_COPY
SNDR_COPY_CMD SNDR_QUIET SNDR_VERBOSE
There is no configuration file. Set env vars, or pass flags.
Per-subcommand binaries
If you compile with the default infer-command feature, you can
symlink the binary under one of three special names and it dispatches
to the matching subcommand:
Security
What the server sees
- Ciphertext bytes of your file.
- A few plaintext fields needed to route the share: file ID, nonce, owner-token hash, expiry timestamp, download counter, encrypted size.
- The IP address of every uploader and downloader.
What the server cannot see
- The file's contents, filename, or MIME type. All three are encrypted client-side before upload.
- The URL fragment (
#...), which is the decryption key. HTTP does not include the fragment in requests, andsndris careful to preserve it across redirects.
Threat model in one paragraph
sndr is safe to use over a hostile network: TLS protects the
ciphertext in flight, and the operator never holds the key. It is
also safe against a curious operator: they can see ciphertext but
not decrypt it. It is not safe against a compromised operator
who can swap the web client's JavaScript: when you open a share
link in a browser, the server's JS reads the fragment and could
exfiltrate it. sndr itself never runs server-supplied code, which
is the whole reason to prefer the CLI for sensitive shares.
The decryption key is in the URL. Treat the URL like a password.
Don't post it in a public channel. Don't paste it into a service that
keeps URL history. If you have any doubt, add --password so the
recipient also needs a passphrase you send through a different
channel.
Hardening this fork carries vs. upstream ffsend
- Server-supplied filenames are reduced to their basename before
being joined with the user's output directory — a malicious
uploader cannot escape your target dir via
..or absolute paths. - The history file is reopened with mode
0600on every save (not just at create time), defending against an attacker who pre-creates the file world-readable. - An inverted-condition bug in
History::removewas fixed; deletes and expired-entry cleanup now persist on autosave. follow_urlpreserves the URL fragment across HTTP redirects, so the decryption key survives thetarnover/send/download/→/dl/301-redirect path.
For broader Send-protocol crypto details see the upstream encryption notes. To report a vulnerability in this fork, see SECURITY.md.
Scripting
sndr is designed for unattended use. The three flags you almost
always want in a script:
-I/--no-interact— fail fast instead of waiting for stdin-y/--yes— assume yes for confirmations-q/--quiet— print only the share URL on upload
URL=
Or set the equivalents globally and forget about them:
sndr exits non-zero on any failure, including expired shares and
network errors, so set -e works as expected.
Build
Feature flags (all enabled by default unless marked):
| Feature | Default | What it does |
|---|---|---|
send3 |
✓ | Send v3 protocol (current servers) |
send2 |
Send v2 protocol (older servers) | |
crypto-ring |
✓ | Pure-Rust crypto backend |
crypto-openssl |
OpenSSL crypto backend (needs system OpenSSL) | |
clipboard |
✓ | --copy support via xclip / xsel / native API |
history |
✓ | Local share history |
archive |
✓ | Tar directories on upload, untar on download |
qrcode |
✓ | Render share URLs as QR codes |
urlshorten |
✓ | Built-in URL shortener integration |
infer-command |
✓ | sndrput, sndrget, sndrdel symlink dispatch |
no-color |
Disable colored output |
Override with cargo install --no-default-features --features ....
Migrating from ffsend
If you already had ffsend installed and configured, here's the
quick migration. Nothing happens automatically — sndr is a clean
break, not a drop-in.
| What changes | Old (ffsend) |
New (sndr) |
|---|---|---|
| Binary name | ffsend |
sndr |
| Env-var prefix | FFSEND_* |
SNDR_* |
| History file | ~/.cache/ffsend/history.toml |
~/.cache/sndr/history.toml |
| Symlink dispatch | ffput / ffget / ffdel |
sndrput / sndrget / sndrdel |
| Default host | https://send.vis.ee/ |
https://snd.dx.pe/ |
Carry your share history over (one-time):
Rename env vars in your shell rc:
sndr and ffsend can coexist — different binary names, different
config paths — so you can roll back at any time by switching back to
the old binary.
Project status
This fork is maintained by tarnover to
support the tarnover/send server
fork. Upstream timvisee/ffsend
remains the canonical implementation for users who want the
broadly-packaged binary.
mozilla/send → timvisee/ffsend → tarnover/sndr (this repo)
Bug reports and PRs welcome at github.com/tarnover/sndr. See CONTRIBUTING.md for the contribution flow and SECURITY.md for vulnerability reporting.
Acknowledgements
- Mozilla — designed and open-sourced the original Firefox Send protocol and codebase before discontinuing the service in 2020.
- Tim Visée (@timvisee) — kept the
protocol alive as both the
timvisee/sendserver fork and the originaltimvisee/ffsendCLI that this rebrand started from.
License
GNU GPL-3.0. See LICENSE.