Skip to content

Dev-only gates spelled NODE_ENV !== 'production' still open in a real production deployment that never sets NODE_ENV (os serve does not force it, os start does) #11113

Description

@os-warren

Filed as the declared residual of #10366, whose maintainer ruling (2026-08-22) is being implemented as literally ruled. Filing rather than silently tightening, per the disposition precedent in #11035. Unassigned and unlabelled — this is for triage, not a grade.

The residual

#10366 gates the localhost trusted-origin substitution in
packages/plugins/plugin-auth/src/auth-manager.ts on the ruling's literal
predicate:

process.env.NODE_ENV!=='production'

When NODE_ENV is unset, undefined !== 'production' is true. So a real
production deployment that never sets NODE_ENV is still treated as
non-production and still receives the development convenience.

The ruling's literal text governs, and tightening it to === 'development'
would be a stricter boundary than ruled — it would break test and
unset-NODE_ENV development flows. So #10366 implements it as ruled and
records the gap here instead.

Measured — the gap is narrower than it first reads, and it is not zero

Re-derived on origin/main @ 24043c290:

  • os start forces it.packages/cli/src/commands/start.ts:347
    if (!localEnv.NODE_ENV) localEnv.NODE_ENV = 'production';, with the comment
    "NODE_ENV is only forced to production when the user has not set it."
    A deployment booted through os start therefore lands on production without
    the operator doing anything. packages/cli/src/commands/doctor.ts:213 already
    advertises this as settled ("what os start already forces").
  • os serve does not. No equivalent assignment exists in
    packages/cli/src/commands/serve.ts. os serve is also the only production
    AuthManager construction site in this repo (enumerated on AuthManager trusts localhost wildcards for CSRF in production whenever trustedOrigins arrives empty — measured #10366). A
    deployment run through os serve with NODE_ENV unset is production in every
    sense except the one the predicate can see.

So the residual is real but confined to the os serve shape.

Why this is worth its own card rather than a wider predicate

The pattern is not local to one call site. auth-manager.ts alone carries
several spellings of the environment test — !== 'production',
=== 'production', === 'development' — and the !== 'production' ones all
share this open end. Whatever is decided here should be decided once for the
family, not per gate:

  1. Make os serve default NODE_ENV to production the way os start
    already does — one line, moves every !== 'production' gate at once, and
    makes the two boot commands agree.
  2. Introduce a single reusable predicate that treats unset as production
    for security-relevant gates specifically, leaving DX gates on the current
    spelling.
  3. Accept it and document that NODE_ENV must be set explicitly for os serve
    production deployments.

Not claimed. I did not assess exploitability, and I did not survey which
other !== 'production' gates in the tree would change behaviour under options
1 or 2 — that survey is the bulk of the work and belongs to whoever takes this.
The relevant property, stated without recipe: a gate whose intent is "not in
production" currently fails open when the environment is undeclared.

Related

Recorded from the os-dev seat implementing #10366; every line number above was
re-derived in a worktree off 24043c290.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions