DEV Community

Discussion on: qrdrop: what 'no server' forces you to build

Collapse
 
stan-ely profile image
Bevan Stanely •

Thanks — "features have tests; promises usually don't" is better than anything in the post.

The negative assertion. Not there. test/room.test.mjs asserts that every pairing announces a fresh public key, and that a third peer's frames never reach the receiver — but nothing asserts the flat the secret does not appear anywhere on the wire, as topic or payload. Different test, fails earlier and for a dumber reason. The fake network the suite uses already records everything sent, so it's cheap. Adding it.

SAS: 64 emoji, 4 shown, 24 bits. Your number. Each emoji carries a spoken name (apple, cactus, owl, taco…) picked for no near-homophones and no shared first syllable — the name is the real SAS content, the emoji is decoration.

Is a mismatch fatal? Effectively yes, but by accident. There's no retry on the verify screen. Cancel unwinds the session — room closed, back to the file picker — and the only way to a second pairing is to send again, which calls generateSecret() for fresh bytes. The old code is never re-offered, so it's one roll per secret.

What I'll concede: that's emergent, not enforced. Nothing states it as an invariant, and no test would catch a future "try again" button reintroducing exactly your loop — which is a tempting button, because a genuine pairing failure looks identical to a user. It's also unnamed in the UI: there's only a neutral Cancel, no "they don't match". Both worth fixing.

Room lifetime: one-shot for pairing, with an open window before it.

  • Topic is HKDF(secret, "topic") — stable for the life of the secret.
  • Pairing latches on first arrival: first peer to send its ECDH key is committed to, later ones ignored, others' frames dropped.
  • But that latch is the only thing closing the window, and first-to-arrive can be the attacker. The SAS is what catches that.
  • The window lasts as long as the tab sits on the QR, unpaired. No TTL.

So a QR in a recording is live exactly while the sender is still waiting for a scan — which is the same moment the screen share is running. Once a pairing settles it's spent. Rotating the code on a timer would close that properly; it isn't built.

All three are filed, with your framing rather than mine: #2 the negative assertion, #3 naming the mismatch, #4 documenting the exposure window.

On the LAN video: same move. "Block it at the router and watch it still work" has the shape of deriving the topic — replace trust me with something a stranger can check. My nearest equivalent is a strict CSP plus shipped source maps, so claims can be diffed against what's running. Weaker than yours, because almost nobody will run it. I don't have a ten-second physical test for the browser path.