feat(be): register the browser a session was created from - #4242
Open
sea-snake wants to merge 1 commit into
Open
feat(be): register the browser a session was created from#4242sea-snake wants to merge 1 commit into
sea-snake wants to merge 1 commit into
Conversation
|
✅ No security or compliance issues detected. Reviewed everything up to cb1da49. Security Overview
Detected Code Changes
|
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 19, 2026 01:47
9192b92 to
1c9d6e9
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 19, 2026 02:57
1c9d6e9 to
95afa5f
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 19, 2026 06:28
95afa5f to
e4b6ba5
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 19, 2026 07:07
e4b6ba5 to
28bc1a6
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 19, 2026 08:17
28bc1a6 to
3955e8e
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
2 times, most recently
from
August 19, 2026 10:21
01d618a to
d6d824c
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 20, 2026 10:57
d6d824c to
2ec6d25
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 20, 2026 13:08
2ec6d25 to
506a7b7
Compare
sea-snake
force-pushed
the
feat/session-devices-registry
branch
2 times, most recently
from
August 21, 2026 08:54
45b4a7a to
d28dcbf
Compare
Sessions are per account, so a user who wants to sign one browser out has
nothing to name it by. Anchors gain a device registry:
`{id, key, pending, name, created_at, last_used}` per browser, capped at 20
because the anchor blob is read on nearly every authenticated path, with a
monotonic per-anchor allocator so ids are never reused.
A browser is identified by a public key it holds, and rotates that key at every
sign-in: `key` is what it presented last, `pending` the successor it announced,
and either resolves to the entry. Presenting the successor promotes it and
retires the key it replaces, so a browser profile copied off disk cannot keep
signing in alongside the original — whichever authenticates second presents a
retired key and shows up as a new browser.
Accepting both values is what makes a lost response harmless. A browser advances
to its successor only once a sign-in has succeeded, so an unanswered call leaves
it proving with the key the entry still holds rather than looking like a new
machine.
An announced successor that another browser of the same anchor already holds is
refused. Presented keys are visible on the wire, so without that a caller could
announce a key another browser is about to present and take over its entry when
it does.
The id never changes across rotations, which is why rotating costs a session
nothing: sessions record the id, not the key. It is also what
`revoke_device_sessions` and `identity_info` name, so neither has to carry a key.
At the cap the least recently used record is dropped rather than the
registration failing, which costs that browser its name in the session list and
never costs anyone a sign-in.
Eviction orders on `last_used`, not on `created_at`. Clearing browser storage
loses the browser's key, so each wipe enrols a fresh record; ordering by
enrolment would spend the cap evicting the browsers a user actually signs in
from while the churn survives, and since eviction also ends the dropped
browser's sessions, that signs them out on a device they never touched. Ordering
on use makes each wipe's throwaway records evict each other instead.
`last_used` is also what the settings list wants to read: "last used" is the
question someone deciding what to sign out is asking, and enrolment does not
answer it.
Devices live on the anchor, so they ride on `identity_info` alongside
`mcp_config` rather than needing a call of their own.
Implements the registry in docs/ongoing/revocable-app-sessions-spec.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sea-snake
force-pushed
the
feat/session-devices-registry
branch
from
August 21, 2026 10:38
d28dcbf to
cb1da49
Compare
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.
Design: #4224. Overview: #4230.
Ships with #4243, which proves and rotates a browser's key, and #4249, which renders the list.
Motivation
Sessions live per account, so a user who wants to sign one browser out has nothing to name it by. A browser registry groups a browser's sessions across apps so they can be revoked together.
Changes
StorableAnchorgainssession_devices(field 7) andnext_session_device_id(field 8), bothOptionso existing anchors decode cleanly. Each entry is{id, key, pending, name, created_at, last_used}, capped at 20 because the anchor blob is read on nearly every authenticated path.keyis the browser's current public key andpendingthe successor it announced last time. Either resolves the entry, and presenting the successor promotes it, retiring the key it replaced. That is what makes a browser profile copied off disk stop working: the copied key dies as soon as the real browser signs in again.SuccessorAlreadyInUse). feat(be): mint short-lived app delegations from a revocable session #4243 adds the stronger rule — proof of possession of the successor — which closes the takeover this was originally for; what remains here is the invariant that one public key belongs to at most one entry, so resolving a presented key never depends on list order.revoke_device_sessionsandidentity_infoname, and a caller never supplies it. Ids come from a monotonic per-anchor counter and are never reused, which is why a flood of registrations leaves a permanent gap in the sequence rather than a list that looks untouched.last_used, notcreated_at. Clearing browser storage loses the key, so every wipe enrols a fresh entry and the wiping browser always holds the newestcreated_at— under enrolment order it would never be its own victim, so twenty wipes would evict twenty browsers the user actually signs in from.identity_infoalongsidemcp_config. New candid typeSessionDeviceInfoand anoptfield, backwards compatible in both directions.Tests
session_device_tests(17). Registration and reuse: an unseen key registers; a key the anchor holds reuses the entry and leaves its name alone; ids are never reused; registration stamps both timestamps; reuse advanceslast_usedonly.Rotation: a successor is accepted and takes over from the key it replaces; the replaced key is then a new browser; the current key still resolves when a response was lost; ten rotations keep the same id; re-announcing the same successor is allowed, because a retry does that; a browser that never rotates keeps working.
The invariant: a successor matching another browser's current key, or its announced successor, is refused and registers nothing.
The cap: registering past it drops the least recently used, never fails, and reports which id it dropped; a browser enrolled first but used most recently survives a registration a newer-but-idle one loses; twenty storage wipes interleaved with use of one browser leave that browser listed.
PocketIC
should_report_no_session_devices_for_an_anchor_from_the_previous_releaseinstalls the previous release, registers an anchor, upgrades, and asserts the new field decodes as absent with everything else intact.