Skip to content

List and remove Hosted Relay passkeys from the account page - #1244

Merged
nedtwigg merged 3 commits into
mainfrom
hosted-passkey-burrow-scope
Oct 20, 2026
Merged

nedtwigg merged 3 commits into
mainfrom
hosted-passkey-burrow-scope

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

Hardening for Hosted Relay passkeys: an account can now see and remove them.

Until now a passkey on the Hosted Relay could only go away with its whole account. Any computer enrolled on the account can mint setup codes, so a passkey registered through one (whoever ran that computer, or redeemed a code it showed) stayed on the account even after the computer was removed, counted toward MAX_PASSKEYS_PER_ACCOUNT, and could keep signing in.

  • Migration 009_relay_passkey_removal.sql. dormouse_relay_passkeys."burrowId" names the Burrow whose setup token registered the passkey (ON DELETE SET NULL). dormouse_relay_sessions."credentialId" names the passkey that signed the session in (ON DELETE CASCADE). Existing passkeys have no Burrow, and existing sessions name no passkey, so they live out their 12 hours.
  • Relay (hosted/server/relay-api.ts). setup/finish records the minting Burrow. setup/finish and signin/finish record the session's passkey. A full account's 409 now names the account page.
  • Account routes (hosted/server/relay-account.ts). GET /api/relay/passkeys lists the account's passkeys: label, createdAt, and the enrolled Burrow that added each one. DELETE /api/relay/passkeys/:credentialId deletes the row, and every session it signed in (and their presence nonces) goes with it. Both use the same cookie gate as the Burrow routes: exact Origin for the delete, scoped to the login's account, 404 for another account's passkey.
  • Account page (hosted/src/App.tsx). A Passkeys section under Computers, with Remove, says which computer added each passkey.

Removing a computer deliberately keeps the passkeys it added; they just lose the attribution. Pocket signs in to every computer on the account with one passkey (a signed-in phone retires a later computer's setup code instead of registering again), and each computer's pairings pin that passkey. Cascading would sign the phone out of the other computers and force it to pair with each one again whenever an old laptop is retired. The rationale entry says why.

The self-host Relay is unchanged: its passkeys belong to the operator's single account, in the operator's own state files.

Tests

  • hosted/server/tests/relay.test.ts, "a passkey names the computer whose code registered it and outlives it; removing the passkey ends its sessions". Setup and sign-in sessions name their passkey. Removing the computer leaves the passkey unattributed and still signed in. Deleting the passkey ends its sessions and nonces, and its sign-in then 404s; another passkey's session stands. The test goes red if the session's credentialId is dropped.
  • hosted/server/tests/workers.test.ts, "the account lists its passkeys with the computer that added each, and removing one ends its sessions". Checks the list and attribution, that an unentitled account gets 403, the origin gate, the 404 for another account's passkey, and the session cascade.
  • boundary.test.ts lists the two new account routes. migrations.test.ts pins 009. The cap test expects the new 409 message.

Specs

  • docs/specs/hosted.md. In "Burrow enrollment": rows for the two routes; Never remove a passkey with its Burrow; Must remove a passkey by deleting its row. In "Relay": the setup row records the Burrow. Word budget ratcheted.
  • docs/specs/hosted.rationale.md. The passkey and session cap entries no longer say that only the account grows those rows. A new entry explains why a passkey outlives its Burrow.
  • docs/specs/security-hosted.md. S100 is widened: a removed passkey's sessions must not survive, and removal must not reach another account's passkey. 009 is added to the files to inspect.
  • docs/specs/security.md. The enrollment-approval bullet now says what approving someone else's code lets their computer do, and how to undo it. The setup-QR bullet points at the account page. Word budget ratcheted.
  • New canary seed .github/audit/canaries/passkey-removal-keeps-sessions.patch (Rule: S100).

This PR touches .github/, so the owner merges it.

🤖 Generated with Claude Code

A passkey on the Hosted Relay now records the computer whose setup code
registered it, and every Relay session records the passkey that signed it
in. The account page lists the account's passkeys, each with its label,
when it was added, and the enrolled computer that added it, and removes
one: its row goes, and every session it signed in (and their presence
nonces) with it. Removing a computer keeps the passkeys it added, no
longer attributed, since one passkey signs a phone in to every computer
of the account and each computer's pairings pin it.

A full account's 409 now names the account page.

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

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

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: c4839fa
Status: ✅  Deploy successful!
Preview URL: https://a7cebc9a.mouseterm.pages.dev
Branch Preview URL: https://hosted-passkey-burrow-scope.mouseterm.pages.dev

View logs

@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.

No findings on the diff. Approval is withheld only because Workflow Lint is red at this head: zizmor flags ci.yml:403 ("action's hash pin has mismatched or missing version comment"). This PR doesn't touch ci.yml, and the same job fails on main at ed40d010de8018e77df99a160658e0d052c33e59 (run), so the fix belongs on main. All other checks are green.

@nedtwigg
nedtwigg merged commit cdf71a9 into main Oct 20, 2026
10 checks passed
@nedtwigg
nedtwigg deleted the hosted-passkey-burrow-scope branch October 20, 2026 16:33
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