qrdrop sends a file straight from one device to another. The file is encrypted
end to end and never touches a server: pairing happens by scanning a QR code or
reading out a short code, and the transfer itself runs peer to peer. Before
anything is sent, both ends show the same short emoji phrase and each person
confirms the other's out loud, so a stranger who has the pairing code still
cannot get in the middle without one of you noticing.
There is also Beam, which moves a file as animated QR codes across a camera and
needs no network at all.
This installer is NOT code-signed. There is no Windows code-signing certificate
behind this project, so SmartScreen may warn about it. That warning is accurate:
nobody has vouched for this binary. It does carry a build provenance attestation,
which is a verifiable claim about where it was built and not a signature:
gh attestation verify <file> --repo stan-ely/qrdrop
README
qrdrop
Send a file straight from one device to another.
A QR code carries the key; the file goes over WebRTC;
nothing in between ever holds a readable copy.
.deb and AppImage on Releases — Beam only; the webview has no WebRTC
A file sent from the CLI can be received in a browser, and the other way round.
That interoperability is the reason this is one package rather than three.
One protocol, three runtimes. The same src/core/ runs in a browser, in
Node and in a Tauri app, and is typechecked twice — with and without Node's
globals — so "isomorphic" is a build failure rather than a promise.
Protocol
Keys from a QR code, checked by eye. 32 random bytes in the code, HKDF for
everything else, an ephemeral ECDH per session, per-chunk AES-GCM on top of
WebRTC's own encryption, and a four-emoji SAS that gets exactly one attempt per
secret. WebCrypto only. Protocol ·
Threat model
No server of its own. Public relays introduce the two devices and drop out.
The file then goes straight between them, over WebRTC, a direct local-network
connection or QUIC, whichever is fastest and up.
Networking, end to end
A fountain code for no network at all. Beam animates LT-coded QR frames, so
a receiver needs enough frames rather than particular ones — 1.58 × N
frames at 10% loss, against 3.89 × N for numbered chunks on a loop, measured
by scripts/bench-beam.mjs.
Beam
No framework, four runtime dependencies. The UI is a custom element over a
hand-rolled virtual DOM, laid out to never scroll, and checked by a script that
walks every screen in Chromium and Firefox, at four viewports, with a mouse and
with touch.
Shipped where people are. npm with provenance, Homebrew, Scoop, the
Microsoft Store, and a self-hosted F-Droid repository whose APK is built
reproducibly — two builds in different directories are byte-identical.
The app
How it works
Two devices in the same room. One shows a QR code, the other points a camera at
it, and that scan is the one channel an attacker cannot stand in the middle of.
The QR carries 32 bytes of CSPRNG output. Everything else derives from it:
The room ID is derived rather than being the secret itself. Using the secret as
the room name is the obvious shortcut, works perfectly in testing, and silently
publishes your key to every relay on the network.
Both devices then show the same four emoji, and the sender confirms they match
before the filename or size is sent. The receiver's Accept is the only other
gesture. Why each of those steps is there, and what breaks without it, is in
docs/protocol.md.
Threat model
Protected: relays and TURN servers see ciphertext, timing and volume only; a
network attacker without the QR cannot join, read or tamper with a transfer;
past transfers stay closed if a code leaks afterwards; truncated, reordered or
altered files are rejected rather than written.
Not protected: anyone holding the code can join — it is the whole
credential, so show it to a person and not to a room or a screen share; both
peers learn each other's IP address; the host serving the page could serve
modified code; relays see that two keys met.
> [!WARNING]
> Beam is not encrypted, and cannot be. There is no handshake on a one-way
> channel. It is for air-gapped machines, where the alternative is a USB stick.
The full version, including how long a code stays useful and what the route
indicator can and cannot tell you, is docs/threat-model.md.
Vulnerabilities go privately through SECURITY.md.
Development
npm install
npm test # unit suite, offline
npm run typecheck # two tsc runs, browser and Node; nothing is compiled
npm run build # esbuild -> site/dist/
npm start # build, then serve site/dist/ on :4173
localhost counts as a secure context, so WebCrypto and the camera both work
against npm start without a certificate. The end-to-end suites
(npm run test:e2e, npm run test:e2e:interop) need a network and public Nostr
relays, so they stay out of npm test and out of CI.
CONTRIBUTING.md has the house style, the source layout, and
the rules a change can break silently.