Skip to content

id(web): registered identities are kept forever — no delete, expiry or cap at any layer, and the register handler is unthrottled #1766

Open

Description

@dezren39

Problem

Every POST /api/identity/register creates a client identity that is kept forever, everywhere it is stored: there is no delete, eviction, expiry, or cap for identities anywhere in the web identity store — and the registration handler has no rate limiting. Tokens expire after 30 days, but the identity record itself does not, and /api/identity/me refreshes a presented token indefinitely, so neither side of the store ever shrinks.

Evidence (developing-today/code @ 9874fe5, pkgs/id/src/web/identity.rs unless noted)

  • register (:180-214) persists the identity first (IdentityDb::create, :541), then inserts it into the in-memory clients map (:198-201) and creates a watch::channel inserted into name_watchers (:205-209). Nothing bounds the count.
  • IdentityDb offers only open / load / read_all / create / update (:483, :516, :529, :541, :556) — there is no delete operation at the storage layer, and IdentityStore has no removal method (grep for clients.remove / retain / any deregister path across pkgs/id/src returns nothing).
  • Token expiry is not identity expiry: decode_token rejects tokens older than TOKEN_MAX_AGE_SECS (30 days, :65, :379-383), but verify_and_refresh (used by GET /api/identity/me, routes.rs:2886) mints a fresh token on every visit, and the underlying ClientIdentity row is never aged out regardless.
  • Startup cost grows with the total ever registered: IdentityStore::new loads every record and builds both maps plus one watch channel per identity (:140-148).
  • The handler (web/routes.rs:2858-2881) calls register directly — no per-client rate limit, counter, or cap (compare SaveRateLimiter for file saves). Registration is reachable by any client that can reach the web UI: the default bind is loopback, but --bind to a non-loopback address is a supported, documented mode (cli.rs:333-339) where the only protection is the optional --web-token (absent it, serve.rs:1138 merely warns).

Verification (this run)

  • Grep audit (above): no deletion/eviction path exists at any layer — handler, store, or DB.
  • Laptop probe ~/git/fleet/probe-2026-10-28-laneA-identity-growth/ mirroring register's insert shapes (clients map + watcher slot per identity, sealed-record size as nonce + JSON plaintext + GCM tag): 100,000 registrations → both maps hold 100,000 entries and ≥13.8 MB of persisted sealed records, monotonically — nothing in the code can ever reduce any of the three.

Why it matters

A long-lived id serve accumulates one permanent identity per browser profile, device, test run, or script that ever registered — plus one encrypted on-disk record and a startup reload of all of them — with no operator-visible count, no cleanup verb, and no bound. On a non-loopback bind without a web token, a remote caller can grow the store and the on-disk .identity.redb at request speed. This is the same unbounded-growth family as #1732 (directory_auth codes), #1713 (SaveRateLimiter) and #1726 (outbox), in the one store that also persists to disk.

Acceptance criteria

  • Identities have a bounded lifecycle — triage pins the policy (e.g. expiry of identities whose tokens have all aged out, an operator delete verb, a registration cap, and/or rate limiting on the register handler) — and the pinned bound is implemented at the store and DB layers (a real IdentityDb delete).
  • Startup no longer retains records the pinned policy considers dead (they are pruned on load or by a sweep), and clients / name_watchers stay consistent with the DB after pruning.
  • The register handler's abuse surface is addressed per the pinned policy (rate limit and/or cap), without breaking first-visit registration for a legitimate new browser.
  • Regression tests: registering past the bound / past the expiry leaves the store at or under the bound, in memory and on disk; a still-active identity is never pruned.
  • Existing identity tests pass (laptop or CI).

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-a)
    • Session / ID: new-issue-creator-code-a run 2026-10-28 13:05 CDT
    • Found via: filed this run
    • Task: Filed after verifying on current main (9874fe5) that web identity registrations have no delete/expiry/cap at any layer (handler, IdentityStore, IdentityDb) and the register handler is unthrottled; laptop probe (probe-2026-10-28-laneA-identity-growth) quantified monotone growth. Triage packet enqueued to repo-health-code-pilot.
    • Expectation: effort/s — triage pins the lifecycle policy (expiry / delete verb / cap / rate limit), then the bounded fix + regression tests in the issue's acceptance criteria.
    • Links: evidence + probe details in the issue body
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 195 remaining items

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · escalated / blocked

  7. added a commit that references this issue on Nov 2, 2026
  8. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    status/blockedQueue or user escalation state
    queue/blockedQueue or user escalation state
    on Nov 3, 2026
  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up (OpenChamber scheduled lane)

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (OpenChamber scheduled lane)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this itemuser/escalatedQueue or user escalation state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions