Repository navigation
Have a removed passkey revoke its pairings on every Burrow - #1527
Merged
Merged
Conversation
A passkey removed from a Relay's account left every Burrow's ACL record active, because nothing on the wire could say it had gone. Adds the Relay half of stage 1 of #331: POST /api/burrow/passkeys/removed on the burrowToken, which answers only those of the asked ids the account does not hold, and a passkeys-changed frame telling connected Burrows to ask. Both Relays can lose a passkey, so both serve it. Hosted nudges from the account Worker after the delete commits, whatever sessions the passkey had, with the RelayRoom's hourly sweep as the backstop to a lost RPC. The self-host Relay's only removal is a hand edit of account.json, so its minute sweep compares the file with what it last read; an absent file answers nothing rather than "every passkey went". The question is HTTP because the RelayRoom opens no database connection and reads no Burrow frame beyond the routing envelope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removing a phone's passkey ended its sign-in at the Relay while every computer kept the pairing, the known gap in security.md. The Burrow now asks at each relay socket's open and on each passkeys-changed frame, and revokes each named credential through BurrowService.revoke under a requester of its own, `relay`, so the access log says who asked. The Relay's answer can only revoke, and needs no passkey assertion: a lying Relay costs re-pairing. Only credential ids of active records reach it. An id the Burrow did not ask about is ignored, and a failed or refused ask revokes nothing, which is also how a Relay older than the route is handled; an older Burrow ignores the frame. No command can claim the Relay asked, since the requester a message may name is now a smaller set than the log's. Asks run one at a time and at least five seconds apart on the Burrow's own clock, so a nudge racing an ask asks once more and a hostile Relay's nudges cost a bounded rate. Promotes stage 1 above the fold in remote-security-model.md and keeps the per-record list under Future. New rules S300 and S301. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…og copy A computer that cannot reach its Relay cannot hear of a removal. That is inherent, so it belongs with the accepted limits rather than among gaps the project means to close. Also pins the Settings log's copy for a Relay-named revocation, and rewraps a comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying mouseterm with
|
| Latest commit: |
4ab7750
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://be900f79.mouseterm.pages.dev |
| Branch Preview URL: | https://issue-331-stage1-passkey-rev.mouseterm.pages.dev |
dormouse-bot
reviewed
Nov 3, 2026
dormouse-bot
left a comment
Collaborator
There was a problem hiding this comment.
One small thing in the access-log copy, inline.
Exporting burrowRequest for passkey-sync.ts changed the context line the burrow-fetch-global-fallback seed patches, and the header comment moved its hunks by a line, so the seed no longer applied and test:audit failed on a stale pool. The seed plants the same defect, a fallback to the global fetch: planted, the test it deletes goes red if restored, and outbound-lint stays green under its reclassified entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REVOKED_BY landed between ACCESS_EVENT_COPY's doc comment and its declaration, so the comment documented the wrong thing. It moves above that comment and is typed Record<RevocationRequester, string>, so a new requester fails to compile rather than logging a bare "revoked". The same insertion had put the self-host route between the setup-token banner and the route that banner names, and Hosted's under the banner for discovery and setup tokens. Each route now has a banner of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
nedtwigg
marked this pull request as ready for review
November 3, 2026 03:57
nedtwigg
added a commit
that referenced
this pull request
Nov 3, 2026
#1527 added the Relay requester on top of the dor verbs this branch removes. Keep the Relay's: RevocationRequester is 'settings' | 'relay', and the service names it, so no message can. Drop COMMAND_REQUESTERS, revocationRequester, and REVOKED_BY's dor copy, and the dor remote mentions #1527 added to the specs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nedtwigg
added a commit
that referenced
this pull request
Nov 3, 2026
S300 audited revocationRequester, which this branch removes, so it now names what holds the rule: #revokeDevice takes no requester from a message and logs settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Removing a passkey ended its sign-in at the Relay while every Burrow kept the pairing, the known gap in
docs/specs/security.md. This is stage 1 of the design merged in #1473: a removed passkey now reaches each Burrow's ACL.Burrow.
lib/src/remote/burrow/passkey-sync.tsasks at each relay socket's open and on eachpasskeys-changedframe. It sends only active records' credential ids, ignores any id it did not ask about, and revokes throughBurrowService.revokeunder a new requester,relay, so the access log says who asked. No command can claimrelay, because the requesters a message may name are now a smaller set than the log's.The Relay's answer can only revoke, and needs no passkey assertion: a lying Relay costs re-pairing, which is availability. A failed or refused ask revokes nothing. An older Relay answers 404 and nothing happens; an older Burrow ignores the frame. Asks run one at a time at least 5 seconds apart, so a hostile Relay's nudges cost a bounded rate.
Both Relays serve
POST /api/burrow/passkeys/removedon theburrowToken, answering only asked ids the account no longer holds. It is HTTP because the RelayRoom opens no database connection and reads no Burrow frame beyond the routing envelope.nudgeBurrowsRPC after the delete commits, with the RelayRoom's hourly sweep as the backstop.account.json, so its minute sweep compares the file with what it last read. An absent file names nothing.Stage 1 moves above the fold in
remote-security-model.mdas "Relay-named revocation"; stage 2 stays in## Future. New rulesS300andS301, with a Guarantee row. A computer that cannot reach its Relay cannot hear of a removal, which moves to What is not defended. Six specs' word budgets are ratcheted.Verified: lib remote suites (1269),
relay(310),remote-lib-common(360), and the Hosted suites on Docker Postgres;tscin lib, standalone, vscode-ext and hosted; the specs, md, e2e, outbound, loopback and public-docs lints. 29 of 29 mutations went red. Not verified: a live Relay, a real phone, or a real Cloudflare deploy. Burrow and Relay have only run against a fake of each other.Deploy: any order is safe. Prefer the relay Worker before the account Worker; otherwise the RPC fails, is logged, and the removal still answers 204.
For review: the 5-second spacing, the 3 retries, and the sweep nudge, which adds one small request per Burrow per hour, are the implementor's calls. A named passkey cuts all its synced devices.
Argos: the 33 changed snapshots in each browser should be the account page's reworded Passkeys help, which is one line longer. Exactly 33 of the 125 Hosted stories render it, counted by rendering each story's args. Nothing here was checked in Argos itself, so the diffs still want a look. No story has a
revokedlog entry, so the Settings log copy moves none.Refs #331. Stage 2 stacks on this.
🤖 Generated with Claude Code