It all runs in your browser — there's no app to install. Your video is encrypted on your device before it ever leaves, so:
Broadcasting needs a publish key — free, about a minute, and it asks you for nothing. Watching needs only a link. How you're protected →
Two browsers, one broker, a relay fleet. Notice that the content key lives only in the share link and is derived on each device — nothing in the middle can decrypt your video.
#k=) that will live only in the link.CK = HKDF(#k=, salts) — computed in the browser, sent nowhere.#k= from the URL.CK, independently, in the browser.Everything in the path is one of these. Only one of them is a secret.
publish keyAdmits you to broadcast. Carries its own expiry under a signature we can't forge and isn't stored anywhere, so it links you to nothing. It can't decrypt. Request one.
route tagProof you were given the link, derived from #k= with different inputs than the content key. It's what a viewer shows to be placed on a relay. Decrypts nothing.
node idAn Ed25519 public key (base32). The stream's identity and the relay track name. Public.
origin EIDWhich relay holds the origin, so a viewer's edge knows where to pull from. Routing only; public.
salts + epochPublic HKDF inputs (a global kill-switch salt ‖ a per-stream salt). Rotating one re-keys the stream.
JWTA short-lived, per-broadcast relay token that authorizes the connection (publish or subscribe scope). Not a content key.
#k= → CKThe secret. 32 bytes in the link fragment (never sent to a server) → AES-256-GCM key via HKDF. Held only by the two browsers.
The design makes every party in the middle either content-blind or removable.
| Party | Can see | Never sees |
|---|---|---|
| Broker (tinymoq.com) | node id, public salts, coarse geo; places you on a relay | your #k=, the key CK, your video & audio |
| Relay fleet | a connection token, encrypted (unreadable) frames, the catalog (codec/resolution) | your #k=, the key CK, your video & audio |
| Someone with the link | your video & audio — the link carries #k=, so anyone with it can watch (share it carefully) | can't publish as you or rotate your key |
| Someone without the link | at most encrypted, unreadable frames — plus the cleartext catalog (codec/resolution) and traffic size/timing | your video, audio, or key — nothing decryptable |
Security you can't switch off beats security you have to remember to switch on.
A relay never holds a key, so this isn't what protects your video — the encryption is. It decides something else: how much an attacker gets if a relay falls, and how much has to keep going right for that to stay true.
The ordinary way to run this is containers on Kubernetes, and Kubernetes can be
locked down well. The trouble is that its security is a set of policies someone has to
choose, apply, and keep applying — and each one has an off switch. kubectl
exec hands you a shell inside a running workload, gated only by a permission
someone can grant. Containers share one kernel, so a kernel bug is an escape, and
the settings that stop a container mounting the host are enforced only if an admission
policy says so. Without a network policy, every workload can reach every other. The
container image itself ships a package manager and a shell, so the post-exploitation
toolkit arrives with the app.
Our relays are Hermit unikernels: a single program in a single address space, booted directly under a hardware-virtualisation boundary. There is no shell to reach — not disabled, absent. No package manager, no filesystem, no users, no second program to pivot to, and no persistent disk; idle relays are reaped and the machine stops existing. None of that is a policy anyone can relax later, which is the whole point: a hardened cluster is a state you must maintain against drift and deadlines, while a unikernel's hardening is a property of the artifact.
One relay carries exactly one stream. Many viewers of that stream share it — that's what a relay is for — but two different broadcasts are never put on the same machine, anywhere. So a compromised relay is confined to a single broadcast by a machine boundary rather than by a setting. Your access token names one broadcast as well, which means even sharing a relay would grant nothing: two independent barriers, not one. And the fleet is given only the public half of the signing key — it can check a token, it cannot forge one.
Where the argument stops. The machine hosting those unikernels is still an ordinary Linux box running a fleet manager — "nothing to log to" is true of the relay, not of the system around it, and that host is the real target. A smaller ecosystem also means fewer eyes and slower patching than the Linux container world. And a unikernel bounds the damage from a bug in the relay — it doesn't make the relay correct. TRUST.md has the full comparison.
Our client is one file. The transport is a library we didn't write and didn't change.
earthseed.js
— one unminified, documented file that runs in the browser as-is (no build step). It does
capture, encode, encrypt, decrypt, decode and render. What you read is what runs.
@moq/net
(Media over QUIC), by Luke Curley.
It is unmodified, but we now serve
our own copy
of it instead of loading it from someone else's CDN. Here's why, and how to check us.
The transport runs on the same page as your content key. Whoever serves that code could swap it for something that reads your key straight out of the link and mails it elsewhere — no bug on our side required. So the only version of it we're willing to run is one we serve ourselves, fixed at deploy time and reviewable in the repo.
Pinning a version on someone else's CDN is not the same thing, which is easy to
miss. A pin names a version, not the bytes behind it — and it only covers the package you
named. @moq/net pulls in four more (@moq/qmux,
@moq/signals, async-mutex, zod) as version
ranges, which a CDN resolves fresh on every request. Pin the top package and a new
release of any of the four still starts running on this page, unchanged on your side.
So we build it once from exact versions and serve it from this origin. Nothing about it is
modified, and you shouldn't take our word for that — the build is reproducible.
The recipe
is four commands; run them and you should get a file whose SHA-256 matches
the one we ship,
which you can also compare against
Luke's upstream source.
It's left unminified so it stays readable. Full reasoning and the honest limits are in
TRUST.md.
Six files, no build step.
Download a copy
and serve it from your own domain. HOSTING.md covers verifying it, and what
it doesn't change.
Encryption this strong has a cost, and it is worth stating rather than discovering.
Because nothing in the middle can decrypt anything, we can't police content — so we don't pretend to review it. What we can do is stop a broadcast: any viewer can report one from the watch page, and an operator decides. Nothing is automatic; "enough reports" would be a weapon aimed at exactly the people this exists to protect.
Terminating means no further relay placement and no further token for that name. Browsers running our client stop within seconds; a session already open ends when its relay token expires and is not reissued. We still can't say what it contained.
The short version: your video is encrypted on your device with a key that lives only in
the # part of your link, which browsers never send to a server. There is no
account, no email and no password. A publish key admits you to broadcast and is never
stored, so nothing connects you to what you broadcast. A viewer must prove they hold
your link before a relay will even be assigned to them — knowing your stream's name is
not enough.
And the limits, in the same place as the promises: a link can't be recalled, a viewer can always record, and your IP is visible to the network. Read the whole thing →