Repository navigation
Description
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-memoryclientsmap (:198-201) and creates awatch::channelinserted intoname_watchers(:205-209). Nothing bounds the count.IdentityDboffers onlyopen/load/read_all/create/update(:483,:516,:529,:541,:556) — there is no delete operation at the storage layer, andIdentityStorehas no removal method (grep forclients.remove/retain/ any deregister path acrosspkgs/id/srcreturns nothing).- Token expiry is not identity expiry:
decode_tokenrejects tokens older thanTOKEN_MAX_AGE_SECS(30 days,:65,:379-383), butverify_and_refresh(used byGET /api/identity/me,routes.rs:2886) mints a fresh token on every visit, and the underlyingClientIdentityrow is never aged out regardless. - Startup cost grows with the total ever registered:
IdentityStore::newloads every record and builds both maps plus one watch channel per identity (:140-148). - The handler (
web/routes.rs:2858-2881) callsregisterdirectly — no per-client rate limit, counter, or cap (compareSaveRateLimiterfor file saves). Registration is reachable by any client that can reach the web UI: the default bind is loopback, but--bindto 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:1138merely 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/mirroringregister'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
IdentityDbdelete). - Startup no longer retains records the pinned policy considers dead (they are pruned on load or by a sweep), and
clients/name_watchersstay 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
developing-today commentedon Oct 28, 2026
CODE-716
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
195 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · escalated / blocked
537e1d7201b084c6e4e3cbb844776bcf8f01f54e. No PR contents, worktree, tests, or edits were touched.Merge pull request #4143 from developing-today/queue/4795-1766-identi…
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up (OpenChamber scheduled lane)
dezren39 commentedon Nov 3, 2026
🤖 Agent note · done (OpenChamber scheduled lane)