Skip to content

Apply revocations at once, retry their saves, and bound refusals in the access log - #1538

Merged
nedtwigg merged 14 commits into
mainfrom
revocation-and-access-log-hardening
Nov 3, 2026
Merged

nedtwigg merged 14 commits into
mainfrom
revocation-and-access-log-hardening

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Hardens how a revocation takes effect and is saved, and what the access log keeps.

Revocation

  • BurrowRuntime.revoke is synchronous and on neither queue, so a stuck ACL write delays only the save. An approval that overtook a revocation re-applies it at publish. If the revocation names the phone being paired, the pairing is refused user-denied and opens no window.
  • BurrowService holds a revocation from the moment it applies until a saved ACL carries it. A write whose snapshot predates the revocation ends no hold.
  • The save retries every REVOCATION_SAVE_RETRY_MS, on any revoke attempt, and at dispose and host shutdown. A stopped Burrow's revocation is now held and retried too; before, a failed save revoked nothing.
  • A revoke answers within that interval rather than waiting on a stuck write. Settings then lists the phone under "Revocations not saved" with Save again, which sends revokeDevice with that phone's id. A revocation the Relay named is held and listed the same way.
  • A revoke that names nothing answers for itself. The held save is tried again behind the answer, and that revoke neither waits on it nor reports its failure.
  • What remains: a save that keeps failing until Dormouse closes is undone at the next launch. It is listed in docs/specs/security.md -> "Known gaps", and the guarantee row in docs/specs/security-remote.md is narrowed to match.

Access log

  • Refusals keep to their own share (ACCESS_LOG_MAX_REFUSALS of ACCESS_LOG_MAX_ENTRIES), so a refusal evicts only an older refusal. Every other event now shares the remaining 400.
  • A connection or pairing on which no Client transport message decrypted logs nothing, so it names no device and does not move Last seen. This also drops the log entry for a phone that handshakes and then gives up without sending anything.

Wording

  • Who adds a push registration: a phone this computer paired, and a revoked one can keep or re-add a row that never receives a push.
  • docs/specs/remote-security-model.md now says "display text" where the ACL record does hold other phone-supplied fields.
  • The availability note in docs/specs/security.md says a revoked phone's session can also hold off new connections.

Compatibility

No wire or on-disk format changes. pairedDevices gains an optional unsavedRevocations, absent when empty and read as none from an older broker. A stored log longer than the new shares is trimmed when read.

Not changed

A store write still has no deadline. A stuck write is waited out and never overlapped, so no retry runs until it ends. A deadline would need a guard against a late write overwriting a newer ACL.

Testing

  • Root pnpm test, tsc -b in lib, and tsc --noEmit in standalone/ and vscode-ext/ pass on the merge with main.
  • Each new test was checked by mutating the committed code and seeing it go red.
  • Two canary seeds on the approval path are refreshed for the new lines.
  • The new UnsavedRevocation story adds one snapshot in each Argos browser, awaiting approval.

🤖 Generated with Claude Code

nedtwigg and others added 9 commits November 2, 2026 20:21
…n decrypted traffic

A refusal now evicts only an older refusal, by event rather than device id.
A pending connection or pairing that ends with no Client transport message
decrypted on it logs nothing, so it cannot move Last seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BurrowRuntime.revoke is synchronous, on neither queue; an approval running
behind it re-applies it at publish. The service holds every revocation from
that moment until a saved ACL carries it, and retries on a timer, on any
revoke attempt, and at dispose and shutdown. An id whose revocation is unsaved
can be revoked again, which saves again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o adds a push row

Narrows the revocation guarantee to what holds while a save fails, and states
what remains as a known gap. Says display text where the ACL record does keep
other phone-supplied fields, and notes that a revoked phone's session can also
hold off new connections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A revoke answers once its save failed or has run past the retry interval, which
is also what puts it in Settings. Folds the save helpers into one, narrows the
held-device helper, shares the row head in the paired-phone lists, and uses
messageOf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iring's log entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 Nov 3, 2026 •

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 32e6138
Status: ✅  Deploy successful!
Preview URL: https://7e4b46e7.mouseterm.pages.dev
Branch Preview URL: https://revocation-and-access-log-ha.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.

This PR overlaps #1530 and #1528. All three edit service.ts #revokeDevice/revoke, acl.ts, access-log.ts, PairedDevices.tsx, and the revocation rows in security-remote.md. #1530 removes dor remote and the by requester. This PR still names dor remote revoke <id> as the way to save an unsaved revocation again, both in its body and in the guarantee row it rewrites. Whichever PR lands second will need that wording reconciled, along with the revocationRequester(params?.by) call in #revokeDevice.

One inline finding below.

Comment thread lib/src/host/remote/service.ts Resolved
…ne's save

An id naming no phone or two, and a target naming nothing, still try the held
saves again, but behind the answer: they neither wait on that write nor report
its failure as theirs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves against the removal of the dor remote command: Settings is now the one
path that saves an unsaved revocation again, and a Relay-named revocation is
held the same way, its id listed there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nedtwigg and others added 2 commits November 2, 2026 21:49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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