06-reference/concepts

fail open vs fail closed defaults

2026-09-06·reference
securityengineering-reviewscribble-works

Fail-open vs. fail-closed defaults

When a config value, env var, or auth check is unset or ambiguous, the system can either fail open (default to permissive — the request proceeds) or fail closed (default to safe — the request is denied). The distinction matters most at the boundary of a paid or gated feature: a fail-open default there silently gives away the thing the gate exists to protect, and it fails silently — nothing errors, nothing logs, the repo can't tell you production is unsafe until someone audits it by hand.

The Scribble Works security/architecture audits found this pattern repeatedly in the same codebase, in unrelated subsystems: a Twilio webhook, the generative session gate, the sign-in rate limiter, and KV-backed usage counters. Some of these were correctly fail-closed by design (documented degradation paths); others — GENERATIVE_REQUIRES_ACCOUNT, AUTH_ALLOWLIST — fail open by omission, meaning an unset var in production silently disables the gate rather than blocking access.

Why this is in the vault

This recurred across four independent audit write-ups on one project (architecture, security, charter-alignment, tier-line-plan) as the same underlying bug shape each time, which is the signal worth a standing name rather than re-diagnosing it per subsystem.

Mapping against Ray Data Co

Reinforces the studio-charter's hard-gate discipline ([[2026-08-31-studio-charter]]: engineering may build reversible things freely but production-affecting defaults are a hard gate) — a fail-open default is exactly the kind of change that should require the same scrutiny as a production deploy, not ride in as an unset env var. It also maps onto [[2026-09-06-scribble-works-trust-ladder]]: every fail-open instance found so far sits at a rung boundary (rung-2 account gate, rung-2 sign-in limiter), so the two patterns compound — a fail-open default at a rung boundary doesn't just leak a config bug, it leaks the monetization boundary itself.

Where this appears