Ferry

Security model

How Ferry keeps your data safe

There is no trusted third party in Ferry, so every rule is enforced independently by the machine that cares about it, against cryptographic evidence rather than a server's word. Here is what that means in practice.

Pre-release

The protocol and the storage format aren't frozen, and no outside security review has happened yet. Treat Ferry with that in mind until 1.0.

01

The identity key

Every machine has one ed25519 identity key, generated the first time the daemon runs. It is never written to disk in the clear. By default it lives in the OS keychain — macOS Keychain, Windows DPAPI, Linux Secret Service. A headless box with no desktop keychain keeps it in an age-scrypt-encrypted identity.age file instead, unlocked by a passphrase from the environment or a file; the wrong passphrase fails the boot.

02

The pairing ceremony

Two machines only become peers through a deliberate exchange. One side runs ferry pair listen and reads a short numeric code across to the other, who types it into ferry pair connect. A SPAKE2 exchange over that code derives a shared secret, and both screens then display the same verification phrase from a fixed word list. Both people confirm it matches. The step is never skippable, never defaulted to yes, and never bypassed by “trust this network”. A mismatched code or a declined phrase aborts with nothing written.

03

Roster enforcement at the handshake

Every connection opens with a Noise XK handshake. The responder resolves the initiator's static key against the local roster and drops the connection if it isn't there — before a single frame is parsed as a command. This is enforced in the transport layer. The networking crate cannot import the crypto crate, so the roster check enters as an injected predicate over raw key bytes: networking enforces it mechanically and never enters transport mode without a pass, and the domain layer supplies the actual decision. Neither layer can be turned into trusting the other by accident. A hostname on a network is an unauthenticated claim; discovery is not trust.

04

Sealing and burn-after-read

Payloads are sealed with age. A secret item gets its own throwaway data key on top of that. “Burn after reading” genuinely destroys it: the wrapped key first, then the ciphertext, then the row. An interrupted burn is designed to leave unopenable ciphertext — never an openable orphan. A plaintext secret is never written unencrypted to disk, never logged, and never included in a crash report.

05

Expiry on the receiver's clock

TTL and burn-after-read are measured on the receiver's monotonic clock, starting at the moment of delivery — never on an absolute timestamp the sender supplied, because a sender's clock is attacker-controlled and two laptops on a hotspot disagree about the time anyway. A ten-minute secret that sat queued for twenty minutes still gets ten readable minutes after it lands. If the monotonic and wall-clock estimates disagree after a restart, the item expires. A corrupted or hostile deadline far in the future is rejected rather than treated as an unbounded TTL.

06

A content-free audit log

The audit log records that an event happened — the actor, the kind, the item id, the time, the outcome — and never what it contained. Pairing, roster imports, deliveries and opens all write a row. The one exception is deliberate: if writing the “opened” row fails on an I/O error, a burn is still never reported as failed over it.

07

Direct delivery only

No machine ever holds, forwards, or relays ciphertext addressed to a third party. Both ends have to be reachable on the same network. There is no relay and there never will be — not even temporarily.

08

Primitives, not constructions

The cryptography is assembled from primitives other people have reviewed: age for sealing, Noise XK for the transport, SPAKE2 for pairing, ed25519 for identity and roster signatures. No novel constructions, no hand-rolled key derivation.

Scope

What a security report should cover

In scope

The pairing ceremony, the Noise transport, roster enforcement, how payloads are sealed, burn-after-read, expiry and TTL handling, the audit log staying content-free, the IPC boundary, and both identity keystores.

Not in scope

A machine that's already compromised. Ferry protects data in transit and behind a locked keychain or an encrypted identity file. It can't help a box running malware as you, or someone who already has the file keystore passphrase.

Found something? Don't open a public issue. Email [email protected] with what the problem is and roughly where, how to reproduce it, and what someone could do with it. You'll hear back within a few days; please give it a reasonable window to be fixed and released before writing about it publicly.