Skip to content

Show and store only the device labels Pocket sends at pairing - #1163

Merged
nedtwigg merged 4 commits into
mainfrom
pairing-known-labels
Oct 17, 2026
Merged

nedtwigg merged 4 commits into
mainfrom
pairing-known-labels

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

The laptop now shows and stores a pairing device label only if it is one of the two names Pocket sends. Any other label becomes "Unknown device", as one-time connection already does with its own set.

Why

The pairing modal draws the phone's label right above the two-digit input. The phone chooses both the digits and the label, so a label like "iPhone · code 58" reads as an instruction, and the person types the sender's digits instead of reading them off their own phone.

The rationale had dismissed this for pairing, because a request needs a presence proof from a passkey on the owner's account. That doesn't narrow who can send one: redeeming a setup QR registers the redeemer's own passkey on the account and signs it in. This holds for setupFinish in both relay/src/app.ts and hosted/server/relay-api.ts. So whoever redeems a leaked QR first passes the proof. That is the case the reverse code exists for, since the owner's phone finds the invitation reserved and shows no code.

What changed

  • PAIRING_DEVICE_LABELS ('Dormouse Pocket (Home Screen)', 'Dormouse Pocket (browser)') and knownPairingDeviceLabel are new in remote-lib-common/src/security/e2e-ceremony.ts, beside one-time's set. Pocket's deviceLabel() is typed to the set, so the two cannot drift.
  • The Burrow maps the label on receipt, so the modal, the PairingQueueItem, and the saved ACL record all get the mapped value.
  • Labels from records already on disk are mapped again wherever they're shown: the held pane's strip and the push device list. An older record with any other text shows as "Unknown device". Every Pocket build since 2026-07-29 sends one of the two names, so real records display as before.
  • boundedPairingLabel had no callers left, so it is removed. So is the modal's (unnamed) fallback, which can no longer happen. The test harnesses and stories send a Pocket label.

Specs

  • remote-security-model.md → Pairing gains "The pairing modal and the ACL record never carry text the phone chose." The one-time rule now points at it.
  • The rationale's false premise is corrected, with a new entry under Pairing.
  • security-remote.md "Trust boundary" Add auto-update for the standalone app #7 is edited in place, so no rule id moves. It now fails if the label reaches anything except through knownPairingDeviceLabel.
  • remote-api.md, pocket-app.md, and docs/stories/pairing.mdx are updated to match.

Tests

Each new test went red under its mutation:

  • remote-lib-common/test/e2e-ceremony.test.mjs: the exact frozen set; members pass through; labels with digits, near-misses, one-time names, empty strings, and non-strings map to the fallback. Loosening membership to a prefix match turns it red.
  • burrow-runtime.test.ts:
    • The modal label and the saved record use the mapped label. Dropping the mapping on receipt turns it red.
    • A record loaded from disk holding 'iPhone · code 58' names its pane strip "Unknown device". Dropping the mapping at the strip turns it red.
  • service.test.ts: the PairingQueueItem and the stored record.
  • push-delivery.test.ts: the device list maps record labels. The preview test was rewritten, because labels no longer tell records apart.
  • views.test.ts (new): both install modes produce exactly the set.
  • App.scan.test.tsx: pair receives a member.

These pass: remote-lib-common (346 tests); relay (292), whose e2e ceremony test now pairs with a Pocket label; lib vitest run src/remote src/host/remote (1270) and tsc -b; standalone tsc --noEmit; spec, markdown, public-docs, and e2e lints; scripts/security-audit*.test.mjs (154). The pairing-acl-live-before-save canary seed still applies.

🤖 Generated with Claude Code

nedtwigg and others added 2 commits October 16, 2026 23:13
The pairing approval modal drew the phone's free-text label right above
the two-digit input, so a label such as "iPhone · code 58" could tell
the person which digits to type. A presence proof does not narrow who
sent it: redeeming a setup QR registers the redeemer's own passkey on
the account, so whoever redeems a leaked QR first passes the proof.

Pairing now matches one-time connection: PAIRING_DEVICE_LABELS holds the
two labels Pocket sends, and knownPairingDeviceLabel maps anything else
to "Unknown device". The Burrow applies it on receipt, before the modal,
the queue, or the ACL record carries the label, and again wherever a
record's label is shown (the held-pane strip and the push device list),
so a record written by hand or by an older build displays the same way.
Pocket's deviceLabel is typed to the set. boundedPairingLabel has no
callers left and is removed; the modal's "(unnamed)" fallback is dead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: c9a9d19
Status: ✅  Deploy successful!
Preview URL: https://6ae59115.mouseterm.pages.dev
Branch Preview URL: https://pairing-known-labels.mouseterm.pages.dev

View logs

dormouse-bot
dormouse-bot previously approved these changes Oct 17, 2026

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Test fails because of this change: relay/test/e2e-ceremony.test.mjs ("a phone scans, registers off the token, pairs, and is told everything it pins") still pairs with the fixture's default label 'iPhone Safari' and asserts record.label === 'iPhone Safari', while the Burrow now stores 'Unknown device' (job log). The PR's sweep caught the remote-lib-common and lib harnesses but missed this relay one.

Both lines sit outside the diff, so there's no inline suggestion. The fix is to import PAIRING_DEVICE_LABELS from remote-lib-common, default the phone({ credential, label = ... }) fixture to one of its members, and assert the stored record against that member. The other 'iPhone Safari' strings under relay/test/ are passkey labels and are unaffected. I can push that commit if you'd like.

The Argos checks are red on the story change (UnnamedDevice/LongValues replaced by UnknownDevice, plus the Default label), which is the expected visual diff to approve.

@dormouse-bot
dormouse-bot dismissed their stale review October 17, 2026 06:25

CI failed — relay e2e-ceremony test still asserts a free-text pairing label.

nedtwigg and others added 2 commits October 16, 2026 23:29
The Burrow now records any other label as 'Unknown device'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nedtwigg
nedtwigg merged commit 24c432c into main Oct 17, 2026
14 checks passed
@nedtwigg
nedtwigg deleted the pairing-known-labels branch October 17, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants