title: Security incident — tenancy invariant bypassed on rocky.erid.tech (missing ROCKY_SESSION_SECRET) date: 2026-07-02 status: Accepted phase: sec-inc-2026-07-02 (production tenancy invariant restoration) predecessor: docs/decisions/2026-07-01-design-tokens-package.md spec: docs/north-star/intent.md (§ tenancy invariant) plan: (this ADR is the plan) severity: P0
Security incident — production tenancy bypass
Summary
Production rocky.erid.tech served gated routes (/vault, /relay, /agents, /workspace, plus every /api/* under the same prefixes) as an unauthenticated caller with a synthesized admin session. All three legs of the tenancy invariant (Airlock session, HATCH audit, Polar entitlement) were absent for the affected requests. The production smoke suite (added 2026-07-01) did not catch this because every assertion was < 500 — a 200-with-forged-admin is indistinguishable from a legitimate 200 at that granularity.
Detected 2026-07-02 while reviewing smoke output (35 KB Vault body returned to a cookie-less request).
Root cause
console/src/middleware.ts:22-23:
const AUTH_DISABLED =
process.env.AUTH_DISABLED?.trim() === "true" || !SESSION_SECRET;
When ROCKY_SESSION_SECRET is unset (SESSION_SECRET === ""), AUTH_DISABLED short-circuits to true and the middleware forwards every request as:
{ id: "dev", email: "dev@devarno.cloud", role: "admin" }
This local-dev bypass is load-bearing for npm run dev (no airlock round-trip), but a production Vercel deploy missing the env var inherits it silently. The Vercel project for rocky-console was scaffolded 2026-05-08 and ROCKY_SESSION_SECRET was never populated in the Production environment.
Impact
- Any drive-by request to
/api/vault,/api/relay,/api/workspace,/api/sniffer,/api/dashboards,/api/traceoexecuted asrole: admin,email: dev@devarno.cloud. - Producer-side entitlement (
checkPolarEntitlement) and approval (checkAirlockApproval) gates ran against a synthesized identity, defaulting tosolotier and env-stub approval flags — not blocking anything the fake admin could reach. - HATCH audit entries emitted during the window are attributed to
dev@devarno.cloud, not the actual caller — audit log integrity for the affected window is compromised. - North-star invariant "triple-lock is load-bearing today and may not be bypassed" was 0/3 co-present in production.
Blast radius bounded by: (a) domain is not publicly indexed / advertised; (b) /api/vault reads decrypt-with-KMS on demand, not on route hit — an anonymous GET returns metadata, not plaintext.
Remediation (sequence)
- Set
ROCKY_SESSION_SECRETin Vercel Production (Rocky-console project, all three environments). Rotate against the local.env.exampleseed if any development instances share the value. - Redeploy to activate the new env. Verify
curl -sI https://rocky.erid.tech/vaultreturns307(redirect toairlock.devarno.cloud), not200. - Invert the smoke suite for gated routes: assert
3xx, not< 500. Landing (/) is currently unprefixed → defaults tooperatoringetMinRole— it too must redirect. The current/agents renders projection pagetest asserts200; it must move to the authed smoke tier (P3) or be replaced with a redirect assertion. - HATCH lookback: query
agent-audit.jsonfor anyagent.invokedbetween the Vercel project scaffold date (2026-05-08) and the fix timestamp; flag any entry attributed todev@devarno.cloudfrom a non-localhostorigin. If any producer fired for anonymous callers, escalate as a real breach event. - Middleware hardening (follow-up): refuse to boot when
NODE_ENV === "production"andSESSION_SECRET === "". Fail-closed at process startup beats silent bypass at request time. Filed as separate slice; not in scope of this ADR.
Locked decisions
1. Fail-closed in production, keep dev bypass for npm run dev
Do not delete the AUTH_DISABLED branch. Local dev needs it — every contributor would need a real signing secret to run npm run dev, which we've refused since Phase 2. Gate the branch on NODE_ENV !== "production" in the follow-up middleware hardening. Missing secret in production is a deploy-time error, not a request-time bypass.
2. Smoke suite must assert presence of the redirect, not absence of 5xx
The current suite proved "no server error" — a strictly weaker claim than "gate is closed." Preventive assertion inverts the polarity for every gated prefix: unauth GET must be >= 300 && < 400 and location must include the airlock issuer host. A 200 for /vault is now an incident-severity smoke failure.
3. Audit log integrity — do not rewrite
agent-audit.json entries attributed to the synthesized identity stay in the log. Rewriting audit would break MR-7 (append-only ledger). The ADR itself is the pointer that flags the affected window for future readers of the audit.
References
console/src/middleware.ts:22-23— the offending short-circuit.docs/north-star/intent.md— tenancy invariant.console/tests/smoke/production.spec.ts— smoke suite to invert.docs/decisions/2026-05-08-erid-domain-adoption.md— Vercel project scaffold reference.