Repository navigation
Description
Problem
DirectoryAuth's pending email-code map grows forever: an entry is only ever removed when that same address comes back to check_code (or re-issues after the resend minute). A code that is issued and never checked — the normal case for a mistyped address, an abandoned sign-in, or a bot probing many addresses — stays in the map for the life of the process, long past its 15-minute expiry.
Evidence (verified this run on main 8c8d1bbb, line trace)
pkgs/id/src/directory_auth.rs:112—codes: HashMap<String, PendingCode>, keyed by address.pkgs/id/src/directory_auth.rs:157-179—issue_codeinserts/overwrites one entry per address; it never sweeps expired entries for other addresses.pkgs/id/src/directory_auth.rs:186-200—check_codeis the only place an entry is removed (on success, on expiry, or on exhausted attempts) — and only for the address being checked.pkgs/id/src/directory_auth.rs:27—CODE_TTL_MS = 15 * 60 * 1000: entries are dead after 15 minutes but are retained indefinitely.- The neighbouring maps in the same file DO sweep, so the omission is an asymmetry, not a design rule: sessions are swept in
open_session(directory_auth.rs:118,retain(|_, record| record.expires_at > now)), and the signed-request replay guard sweeps inaccept(directory_auth.rs:250, retain onSIGNED_WINDOW_MS). - Callers:
pkgs/id/src/directory_view.rs:1219(confirm) and:1244(sign-in) callissue_codefor an address supplied in the request, so distinct addresses accumulate entries at request rate; nothing else touches the map (grep oversrc/forissue_code/check_code/.codes— onlydirectory_auth.rsand those two call sites).
Why it matters
This is a long-lived server process (a hosted world). The map grows by one entry per distinct address that ever requests a code and does not complete the flow, with no bound and no eviction — the same unbounded-growth class as #1713 (SaveRateLimiter) and #1730 (WaitingPool arrived set), but in the auth surface, where an unauthenticated caller controls the key (the email address). It also contradicts the module's own framing (directory_auth.rs:5-7): pending codes are supposed to live only until "its time is up".
Suggested direction (for triage to pin)
Mirror the session sweep: retain only unexpired entries when issue_code runs (it already receives now), and/or sweep at the top of check_code. Keep behaviour otherwise identical: the resend-minute rule, the 5-attempt cap, and the digest-only storage all stay.
Acceptance criteria
- After a code's 15-minute expiry, its entry is evicted without that address ever calling
check_code(unit test: issue for address A, advance the clock past expiry, issue/check for address B, assert A's entry is gone — e.g. via the map's observable size or a re-issue behaving as a fresh entry). - Unexpired codes still verify, the 60-second resend refusal still applies, and the 5-attempt exhaustion still removes the entry.
- Issue/check behaviour for live codes is unchanged; existing
directory_authtests still pass (laptop or CI).
Dedup
Searched open + closed issues and code search for DirectoryAuth, issue_code, and pending-code/expiry terms before filing: no filing covers this map. #1713/#1726/#1730 are the same unbounded-growth class in other components (web save limiter, outbox, waiting pool), not this one.
Activity
developing-today commentedon Oct 28, 2026
CODE-697
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
5 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done / handed off
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
Merge pull request #3748 from developing-today/queue/4789-1732-code-e…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done
Outcome (task 4789):
DirectoryAuth::issue_codenow sweeps expired pending codes first (retain(expires_at > now), mirroring theopen_sessionsession sweep). Unexpired codes — including attempt-exhausted ones — are retained, so resend throttling and the 5-attempt limit are unchanged; the sweep's opportunistic nature and O(n) retain cost are documented onissue_codeper the triage's guidance (no idle cleanup or hard bound claimed). Two new tests pin the TTL boundary (kept at TTL−1ms, evicted at exactly TTL) and exhausted-attempt retention.Verification:
cargo test --lib directory_auth— 15 passed, 0 failed, on a combined tree (main + the #3751 compile repair + this change). The first run surfaced a pre-existing pkgs/id compile break on main from PR #3738, filed as issue #3751; an independent lane's PR #3754 landed the repair on main in parallel, and the duplicate repair PR #3757 was closed after a salvage diff found nothing unique.