Skip to content

feat(be): register the browser a session was created from - #4242

Open
sea-snake wants to merge 1 commit into
feat/session-record-storagefrom
feat/session-devices-registry
Open

feat(be): register the browser a session was created from#4242
sea-snake wants to merge 1 commit into
feat/session-record-storagefrom
feat/session-devices-registry

Conversation

@sea-snake

@sea-snake sea-snake commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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

  • StorableAnchor gains session_devices (field 7) and next_session_device_id (field 8), both Option so 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.
  • key is the browser's current public key and pending the 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.
  • Accepting both values is what makes a lost response harmless. The browser advances only once a sign-in succeeded, so an unanswered call leaves it proving with the key the entry still holds rather than looking like a new machine.
  • A successor another browser of the same anchor already holds is refused (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.
  • The id never changes across rotations, so rotating costs a session nothing: sessions record the id, not the key. It is also what revoke_device_sessions and identity_info name, 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.
  • At the cap the least recently used entry is dropped rather than the registration failing, and its id is returned so the caller can end that browser's sessions too.
  • Eviction orders on last_used, not created_at. Clearing browser storage loses the key, so every wipe enrols a fresh entry and the wiping browser always holds the newest created_at — under enrolment order it would never be its own victim, so twenty wipes would evict twenty browsers the user actually signs in from.
  • Entries ride on identity_info alongside mcp_config. New candid type SessionDeviceInfo and an opt field, 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 advances last_used only.

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_release installs the previous release, registers an anchor, upgrades, and asserts the new field decodes as absent with everything else intact.

@sea-snake
sea-snake requested a review from a team as a code owner August 19, 2026 01:13
@zeropath-ai

zeropath-ai Bot commented Aug 19, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to cb1da49.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► src/frontend/src/lib/generated/internet_identity_idl.js
    Add SessionDeviceInfo type and session_devices field to IdentityInfo and related structures
► src/frontend/src/lib/generated/internet_identity_types.d.ts
    Augment IdentityInfo with session_devices and define SessionDeviceInfo interface/type
► src/internet_identity/internet_identity.did
    Add SessionDeviceInfo type and session_devices field to IdentityInfo
Enhancement ► src/internet_identity/src/main.rs
    Load and expose session_devices from storage into IdentityInfo (store as session_devices)
Enhancement ► src/internet_identity/src/storage/anchor.rs
    Introduce SessionDevice struct, MAX_SESSION_DEVICES, and session device management logic including resolve_session_device and related fields
► Implement From conversions between StorableSessionDevice and SessionDevice
► Extend Anchor with session_devices and next_session_device_id and related methods
Enhancement ► src/internet_identity/src/storage/storable.rs
    Expose session_device module for storage integration
► src/internet_identity/src/storage/storable/anchor.rs
    Add session_devices and next_session_device_id fields to StorableAnchor (with appropriate CBOR mappings)
Enhancement ► src/internet_identity/src/storage/storable/session_device.rs (new)
    Add StorableSessionDevice struct and (de)serialization support for session devices
Enhancement ► src/internet_identity/src/storage/tests.rs
    Initialize session_devices and next_session_device_id in test anchors for migration compatibility
Enhancement ► src/internet_identity/tests/integration/upgrade.rs
    Add test to verify anchors from previous release decode with no session devices after upgrade
Enhancement ► src/internet_identity_interface/src/internet_identity/types/api_v2.rs
    Import SessionDeviceId and include SessionDeviceInfo in IdentityInfo for API v2
Enhancement ► src/frontend/src/routes/(new-styling)/manage/(authenticated)/(home)/smartActions.test.ts
    Initialize session_devices in test identity info setup

@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from 9192b92 to 1c9d6e9 Compare August 19, 2026 01:47
Copilot AI lite review requested due to automatic review settings August 19, 2026 01:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from 1c9d6e9 to 95afa5f Compare August 19, 2026 02:57
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from 95afa5f to e4b6ba5 Compare August 19, 2026 06:28
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from e4b6ba5 to 28bc1a6 Compare August 19, 2026 07:07
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from 28bc1a6 to 3955e8e Compare August 19, 2026 08:17
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch 2 times, most recently from 01d618a to d6d824c Compare August 19, 2026 10:21
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from d6d824c to 2ec6d25 Compare August 20, 2026 10:57
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch from 2ec6d25 to 506a7b7 Compare August 20, 2026 13:08
@sea-snake
sea-snake force-pushed the feat/session-devices-registry branch 2 times, most recently from 45b4a7a to d28dcbf Compare August 21, 2026 08:54
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
sea-snake force-pushed the feat/session-devices-registry branch from d28dcbf to cb1da49 Compare August 21, 2026 10:38
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