Ferry is a private channel between machines your team already trusts — paired once, verified by a phrase you read aloud, always there after. Send a file, a secret or a message straight from one machine to another: no cloud, no account, no server in the middle, and nothing sitting in a searchable chat log forever. Secrets carry a TTL and burn after the first open; every exchange lands in a content-free audit log instead.
It isn't a chat app and it isn't AirDrop. It's the channel for the things that shouldn't be in either — credentials, keys, anything with an expiry date.
Free and open source, Apache-2.0. One daemon, three ways in: the ferry command line, a VS Code extension, and a desktop app.
# machine A ferry send laptop-b ./build/app.tar.gz ferry send laptop-b ./.env --burn # gone once they open it
Pre-release software
The wire format and the on-disk format are still changing, and none of it has had an outside security review yet. Play with it, don't depend on it — and don't put anything through it that you'd be upset to lose or leak. Compatibility between versions isn't promised until 1.0.
Why bother
A cloud drive, a paste site, Slack, email — the secret lands on a third party's disk, gets indexed, gets backed up, and is one breach, one export or one overzealous search away from being found by someone who never should have seen it. Ferry doesn't do any of that. The data leaves your machine and arrives on theirs, and that's the whole trip. There's no server to break into, no channel history to purge, nothing to subpoena.
The trade-off is real
Ferry only works when both machines can actually reach each other — the same office network, a phone hotspot, a VPN. There's no relay and there never will be, so it won't punch through the internet for you. If that's what you need, Ferry isn't the tool.
Straight answers
How it works
Made the first time the daemon runs, never written to disk in the clear. Kept in the OS keychain, or in an age-encrypted file on a headless box.
Someone reads a short code across; then both people confirm the same verification phrase shows on both screens. Never skipped, never defaulted to yes, never bypassed by “trust this network”.
Every connection starts with a Noise handshake that drops any key not already in your roster — before it reads a single command. Enforced in the transport, never only in the UI.
Files are sealed with age. Secrets get their own throwaway key on top, and “burn after reading” shreds the wrapped key, then the ciphertext, then the row — an interrupted burn leaves unopenable ciphertext, never an openable orphan.
TTL and burn-after-read are measured on the receiver's monotonic clock from the moment of delivery — never on a timestamp the sender supplied. If the clocks disagree after a reboot, the item expires.
It records that something happened, who, which item, when, and the outcome — and nothing about what it contained.
Three surfaces
The daemon is the only thing that speaks the peer-to-peer protocol. Everything you touch talks to it over a local socket and never to another machine directly.
The ferry and ferry-daemon binaries. ferry send, ferry receive, ferry pair, ferry activity — any command starts the daemon itself on first use, except ferry status, which only ever reports the truth.
Brings its own daemon — nothing else to install. A sidebar and a full panel, both the same app: pairing, an inbox, the send panel and a secret composer, all inside the editor.
A Tauri shell around the same UI, for machines without an editor in the loop. The daemon ships inside the installer as its own sidecar — nothing separate to run.
Pairing lives in the CLI, not the headless daemon — it needs a human to read a verification phrase aloud, and a step that structurally needs a person can't live in a process that structurally can't reach one.
The interface
One React app, three hosts. Shown here with mock data.
Opt-in
By default Ferry makes no network call except a direct transfer to a peer. Two features can talk to GitHub's API — and only after you set remote_features_enabled = true and connect an account. Nothing leaves for GitHub before that, Ferry never falls back to GitHub on its own, and leaving it off costs you nothing but those two features.
You choose, per item, to put an already-sealed copy on GitHub as a private gist and hand someone the link — for when you can't get on the same network. The trade-off is real: the ciphertext leaves your machine, burn-after-read can no longer be honoured, and GitHub learns the recipient's public key and the file size.
Point Ferry at a repo file — any URL you control. It fetches the signed roster, shows you who signed it and the exact add/remove diff, and imports only on a second confirmation.
Every exchange with GitHub is a choice you make for that one action. There is no background sync, no telemetry, and no automatic upload.
Architecture
Eight Rust crates in one Cargo workspace. Dependencies only ever point one way, so a violation is a build error, not a code-review habit.
dependency direction
daemon / cli → core → net / store / crypto → proto
crates/proto Types, enums and error codes. Zero logic. The TypeScript bindings are generated from here; CI fails on any diff.
crates/core Every domain decision. Drivable from the daemon, the CLI, or a fake-network test harness without change — never mentions a socket, a database, or React.
crates/net · store · crypto The machinery: framing and transport, SQLite, the audited primitives.
crates/daemon · cli Wire it together and nothing else.
crates/remote The only crate that knows HTTP exists — behind the off-by-default GitHub features.
How “done” is measured
Ferry keeps a verification ledger: one row per user-facing flow, and a row is only green once it's been driven end to end on two real machines on a real network, including a disconnect and reconnect where that matters. Code compiling, unit tests passing, and the two-node harness passing all earn a lower mark — they tell you about the state machine, not about a hotspot with a sleeping laptop on it.
A full pair → send → burn → open cycle runs today between two daemons on one host, and between two containers on a multicast network in CI. Every flow is driven that way before it counts as working.
350+ Rust tests · in-memory two-node harness · two-container CI run · Windows + macOS builds
Getting it
Every channel below is produced from the same tagged release. Pick whichever fits — download an archive, pull an image, or build it yourself.
.tar.gz (Linux/macOS) or .zip (Windows) with ferry + ferry-daemon inside, executable bit intact. Extract, drop on your PATH.
docker pull ghcr.io/firaslamouchi21/ferry, mirrored on Docker Hub. CLI and UI in one image, no Rust or Node toolchain.
.deb / .AppImage / .rpm on Linux, .dmg on macOS, .msi / a setup .exe on Windows — the daemon ships inside. Unsigned for now; see what that means.
Not on the Marketplace yet — grab the platform .vsix from the release and code --install-extension ferry-. Bundles its own daemon.
build from source
git clone https://github.com/firaslamouchi21/Ferry cd Ferry cargo build --release -p ferry-daemon -p ferry cargo test --workspace bash testing/local-two-peer.sh
or the one-liner: ./dev.sh builds the daemon + CLI and opens the UI against them, no Docker or separate terminals.
A first run
ferry daemon start ferry pair listen --name laptop-a ferry pair connect 192.168.1.20:53124 482913 --name laptop-b ferry send laptop-b ./.env --burn ferry receive list ferry receive open <item-id> --out ./.env
ferry send just writes the item to a local queue; the daemon delivers it the next time the other machine is around. A headless box — a CI runner, a NAS — keeps its identity in an encrypted file and gets paired from a laptop before it's deployed.
Contributing
There's a short list of things Ferry won't bend on — no server ever, roster enforced at the handshake, pairing always a code plus a confirmed phrase, real state on every item, content-free audit log, crypto from reviewed primitives only. A change that quietly breaks one gets sent back no matter how useful it is; everything else is welcome.
Apache-2.0, and also sold as packaged builds — contributions come in under the same terms, with a DCO sign-off (git commit -s).
Contact
A bug, an idea, a security concern, or you want to help move Ferry toward v2 — this goes straight to Firas's inbox and you'll get an auto-reply to confirm. For vulnerabilities, use the address in SECURITY.md instead of this form.