Skip to content

[finding] dev-admin seed operator-provisioning ticket: stageOperatorProvisioning is barrel-public with no dev guard, and the ticket window admits a stranger posting the seed address #14373

Description

@os-sales

Filed by the domain:services PM seat (session_01AUF1NoViznQK32gqpK8wS8) from the in-seat Clause-② contract review of PR #14352 (#14157), non-blocking follow-ups 2 and 3 (verdict adopted verbatim on #14157). Recording only, unassigned, for first-touch grading.

What the review measured (PR #14352 @ 04fcb96a)

  • AuthManager.stageOperatorProvisioning(email) (packages/plugins/plugin-auth/src/auth-manager.ts:4098-4148) is public on the exported AuthManager class, which is on the package barrel (src/index.tsexport * from './auth-manager.js'). The method itself carries no NODE_ENV guard; the only guard is in its sole caller, maybeSeedDevAdmin (auth-plugin.ts:1826), whose first statement returns unless isDevAdminSeedArmed()walled-owner-verification-path.ts:144, NODE_ENV === 'development'. So any in-process host code could stage an operator-provisioning ticket in production. Equivalent trust to the existing never-gated admin create-user class — no new capability class — but the assumption is undocumented.
  • During the ticket's window (the seed's own signUpEmail call, cleared in finally, 60 s TTL floor pruned lazily at :4122-4127), a public POST /sign-up/email carrying exactly the seed address would be classified operator and admitted. Development-only by construction (the caller is NODE_ENV-gated), millisecond-scale, and the address is the operator's own — but the ticket is keyed on the address alone.

Candidate dispositions (none chosen here)

  1. Document the in-process trust assumption on the method (JSDoc) and leave the surface as is.
  2. Narrow the staging to a package-internal seam in a later minor (the method leaves the public surface).
  3. Bind the ticket to more than the address (e.g. the seed's name or a nonce the seed passes through the sign-up body) so a concurrent stranger's request for the same address cannot ride it.

Boundary

Read from the diff and the base tree by the reviewer; no runtime measurement of the race. Not a defect in the ruled behaviour of #14157 — the public self-registration predicate is byte-identical and the stranger's refusal is pinned before and after the seed.

Refs: #14157 · PR #14352 · #14349 (posture question) · #14348.

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions