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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions