Who holds which key, and where the protection stops
I run the server and the relay, and neither can read your terminal.
Who sees what
- Your browser
- takes your password and draws the terminal; it holds the delegation that keeps you signed in.
- The relay
- admits only connections my server ticketed, pairs a browser with a machine, and forwards frames. It sees which account and machine a connection belongs to, and the sizes and timing of its frames, never terminal contents or keys.
- The daemon on your machine
- runs the shell, proves it is your machine with its own identity, and encrypts the screen with keys made for that one connection.
- My server, off the line
- runs accounts, your machine list, presence and session tickets. It sees your account, your machines' public identities, IP addresses and timing. It never holds your password, a terminal key, or a private key in the clear.
Sign-in with OPAQUE
Your password never leaves the browser. My server stores an OPAQUE record that can check a sign-in, and keeps no hash of the password. Your account key, an ML-DSA-87 key, is made in your browser; the server keeps only an encrypted copy, which only your password can open.
Each browser you sign in from gets its own 30-day delegation, so a reload or a phone restart doesn't ask for the password again. Revoke each one from Settings.
Linking a machine
Linking takes two secrets: the one-use command from the app, good for five minutes, and the approval link or QR code the machine prints. The second one reaches your browser without passing through my server, so the server can't slip in a machine of its own. You approve from a device you're signed in on, with your password.
The machine's identity is sealed by its Secure Enclave (macOS 14+) or TPM 2.0 when it has one. On a machine that can use neither, linking stops until you ask for software custody with --identity-backend none. Software custody happens only if you ask for it, or on a box, which will always use it. In software, whoever copies the machine's key can speak as it; sealed in hardware, a copied disk isn't enough.
Keys per connection
Every terminal connection starts with a fresh, one-use ML-KEM-1024 exchange, then runs Noise_XXpsk3_25519_ChaChaPoly_SHA512, so traffic is ChaCha20-Poly1305. X25519 inside Noise stays as a classical layer. The post-quantum work happens at setup, never per keystroke or per frame.
The claim stops at "post-quantum hardened terminal sessions": resistance to recording traffic now and decrypting it later.
Signed updates
Daemon releases are one SHA-512 manifest signed with ML-DSA-87. The updater checks the pinned key, the manifest and a rollback floor, and has no skip mode.
The first install trusts HTTPS. The installer prints the release key's fingerprint; compare it with this one, and don't link the machine if they differ:
733e336c becc7559 82591288 a138b3e3 6f74e40e ac916431 cf355b10 c234f059
Not covered
- When you type, how much, and how large the screen updates are.
- A browser or a machine that's already compromised.
- Trust in the JavaScript app.merkur.sh serves you.
- Account APIs, code delivery, daemon control and the relay's own transport, which run on ordinary TLS.
- A break-in to the job that signs releases.
- Certification: Merkur isn't FIPS 140 validated and has had no independent audit.
- A box's disk, once Boxes open. It will sit on my servers, and I'll be able to read it.
Reporting a flaw
Email me at dmitriy.pletenskoy@gmail.com. Say what you found and how to reproduce it, with the version from merkur version or Settings. Fixes ship as a signed release.