You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while reviewing the design for #356/#357 (docs/superpowers/specs/2026-08-01-user-disable-and-email-design.md). Independent of those slices — it is a gap in #308's own coverage.
The gap
CreateUserHandler requires a step-up grant only when the new user's role is Owner:
SetUserPasswordHandler is gated on the same condition — resetting a non-Owner's password needs no step-up (pinned by ResetWorkersPassword_NeedsNoStepUp_OrdinaryAdministrationStaysUngated).
So a stolen Owner access token — good for ~15 minutes — converts into durable access without ever meeting a step-up prompt:
GET /api/v1/users to enumerate the account (ungated).
POST /api/v1/users creating a Manager with an attacker-controlled email and password (ungated — only Owner creation is gated).
The Owner token expires. The attacker keeps a Manager account: farm operations, inventory, reports, "undo, correct, configure, see money" per AuthPolicies.AdminOnly.
Step 2 can be swapped for "reset an existing Worker's password" with the same result and less noise — a real account whose owner will report a lockout eventually, but not immediately.
The reasoning in CreateUserHandler is that only a second Owner "multiplies durable account control past a stolen access token's own lifetime". That is true of Owner creation but not exclusive to it: a Manager also outlives the token, and so does any account whose password the attacker just set.
Why it wasn't caught
#308's design explicitly avoids a blanket prompt, and the Owner/non-Owner line is a reasonable first cut at "what multiplies control." The miss is that durability, not privilege level, is what the step-up gate is defending — and every user-creation and every administrative password reset is durable.
Gate by durability, not role: any operation that mints or resets a credential someone else will hold. In practice that is the same set as (1), stated in terms that generalize to future endpoints.
Shorten the access-token lifetime and accept the residual. Cheapest, weakest — 15 minutes is already short, and the attack needs one request.
Re-prompt per session rather than per operation — one step-up unlocks a short administration window. Middle ground; needs a real design.
(2) is the one worth designing, since it also tells the #355 role-editing slice what to do.
Related, already known
Step-up grants live in an in-process registry (InMemoryStepUpGrantRegistry), so "single-use" holds per replica — #338. That is a separate residual, and it gets more load-bearing as more operations depend on step-up.
Not blocking
#356 and #357 gate their own operations regardless; they neither introduce nor widen this. The spec for those slices records the corrected threat model rather than repeating the original overstatement.
Found while reviewing the design for #356/#357 (
docs/superpowers/specs/2026-08-01-user-disable-and-email-design.md). Independent of those slices — it is a gap in #308's own coverage.The gap
CreateUserHandlerrequires a step-up grant only when the new user's role is Owner:SetUserPasswordHandleris gated on the same condition — resetting a non-Owner's password needs no step-up (pinned byResetWorkersPassword_NeedsNoStepUp_OrdinaryAdministrationStaysUngated).So a stolen Owner access token — good for ~15 minutes — converts into durable access without ever meeting a step-up prompt:
GET /api/v1/usersto enumerate the account (ungated).POST /api/v1/userscreating a Manager with an attacker-controlled email and password (ungated — only Owner creation is gated).AuthPolicies.AdminOnly.Step 2 can be swapped for "reset an existing Worker's password" with the same result and less noise — a real account whose owner will report a lockout eventually, but not immediately.
The reasoning in
CreateUserHandleris that only a second Owner "multiplies durable account control past a stolen access token's own lifetime". That is true of Owner creation but not exclusive to it: a Manager also outlives the token, and so does any account whose password the attacker just set.Why it wasn't caught
#308's design explicitly avoids a blanket prompt, and the Owner/non-Owner line is a reasonable first cut at "what multiplies control." The miss is that durability, not privilege level, is what the step-up gate is defending — and every user-creation and every administrative password reset is durable.
Options
(2) is the one worth designing, since it also tells the #355 role-editing slice what to do.
Related, already known
Step-up grants live in an in-process registry (
InMemoryStepUpGrantRegistry), so "single-use" holds per replica — #338. That is a separate residual, and it gets more load-bearing as more operations depend on step-up.Not blocking
#356 and #357 gate their own operations regardless; they neither introduce nor widen this. The spec for those slices records the corrected threat model rather than repeating the original overstatement.