fix(user): harden the first-boot setup window #30

Merged
nalum merged 1 commit from fix/setup-window into main 2026-08-13 11:40:41 +00:00
Owner

Three defects in the anonymous zero-users window (ADR-0018):

  1. TOCTOU: the founding-admin count check ran outside a transaction — two racing anonymous Creates could both become admin. The write now runs in WithinTx with an in-transaction recount, mirroring ImportData's existing fix. Race test: four concurrent Creates, exactly one wins, losers get the same "setup is complete" verdict.
  2. Timing side channel: X-Initial-Admin-Token was compared with != in both Create and ImportData — now crypto/subtle.ConstantTimeCompare.
  3. Silently unset token in shipped defaults: the server now logs a prominent boot WARN when the install is empty and no EAG_INITIAL_ADMIN_TOKEN is set (no refusal — seed-family, local dev and test helpers keep working). The timoni values and TrueNAS compose now document the window explicitly ("whoever reaches it first owns the install") with a CHANGE-ME placeholder.

ADR-0018 updated with the hardening note. Full suite + integration + timoni mod vet pass.

Fixes #9

🤖 Generated with Claude Code

Three defects in the anonymous zero-users window (ADR-0018): 1. **TOCTOU**: the founding-admin count check ran outside a transaction — two racing anonymous Creates could both become admin. The write now runs in `WithinTx` with an in-transaction recount, mirroring ImportData's existing fix. Race test: four concurrent Creates, exactly one wins, losers get the same "setup is complete" verdict. 2. **Timing side channel**: `X-Initial-Admin-Token` was compared with `!=` in both Create and ImportData — now `crypto/subtle.ConstantTimeCompare`. 3. **Silently unset token in shipped defaults**: the server now logs a prominent boot WARN when the install is empty and no `EAG_INITIAL_ADMIN_TOKEN` is set (no refusal — seed-family, local dev and test helpers keep working). The timoni values and TrueNAS compose now document the window explicitly ("whoever reaches it first owns the install") with a CHANGE-ME placeholder. ADR-0018 updated with the hardening note. Full suite + integration + `timoni mod vet` pass. Fixes #9 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(user): harden the first-boot setup window
All checks were successful
check / web (push) Successful in 1m30s
check / go (push) Successful in 1m49s
a0290959ca
While the install is empty, UserService/Create and
SystemService/ImportData answer anonymously — and that window had three
soft edges. The founding-admin count ran outside the transaction that
wrote the user, so two racing anonymous Creates could both become
admin; the X-Initial-Admin-Token comparison used != and leaked timing;
and the shipped deploy defaults left the token unset without a word,
so an exposed fresh install was silently claimable.

Now the count re-checks inside the same transaction that writes the
founding admin (the pattern ImportData already used), the token
compare is crypto/subtle constant-time on both endpoints, and a server
booting onto an empty install with no token set warns loudly instead
of silently opening the window. Refusing to boot was rejected: the
documented local-dev and seed flows run tokenless on purpose.

Fixes #9
nalum force-pushed fix/setup-window from a0290959ca
All checks were successful
check / web (push) Successful in 1m30s
check / go (push) Successful in 1m49s
to 8e12cfc0ce
Some checks failed
tag / tag (push) Has been cancelled
check / go (push) Successful in 1m47s
check / web (push) Successful in 1m24s
android / build (pull_request) Successful in 5m4s
2026-08-13 11:33:34 +00:00
Compare
nalum scheduled this pull request to auto merge when all checks succeed 2026-08-13 11:34:09 +00:00
nalum merged commit 8e12cfc0ce into main 2026-08-13 11:40:41 +00:00
nalum deleted branch fix/setup-window 2026-08-13 11:40:41 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!30
No description provided.