9 · A secrets program by stages
The Secret Leak Field Manual · 4 min read
Question this chapter answers: What does a realistic secrets program look like, stage by stage?
Secrets maturity is not a product you buy; it is a sequence of habits, each one fixing a specific failure and none of them fixing everything. This chapter stages the journey honestly — including what each stage does not fix, because that is where programs stall.
The stage map
| Stage | Name | What it fixes | What it does not fix | Move on when |
|---|---|---|---|---|
| 0 | Awareness and inventory | "We don't know what keys we have" | Everything else | You can list every credential by provider, scope, and owner |
| 1 | .env hygiene | Secrets entering source control: real values out of commits, history scrubbed once as hygiene (never as the fix), .env.example carries names not values, local defaults documented | Build-time inlining; copies on laptops; anything already public | No secret has entered git in a quarter despite AI-assisted velocity |
| 2 | Platform environment variables | Source-to-deploy coupling: deploy-time injection, per-environment scoping, preview environments getting their own (non-production) values | Client-facing inlining (NEXT_PUBLIC_-style prefixes publishing server values); sprawl across many apps | Adding an app no longer means copying secrets anywhere by hand |
| 3 | Manager or vault | Sprawl and access: one store, access control, audit trails, rotation APIs, sharing without pasting | Browser bundles: a vault-stored value inlined into client JavaScript leaks identically | New services onboard through the manager by default; humans rarely touch raw values |
| 4 | Short-lived credentials | Durability itself: cloud roles assumed per-session via OIDC federation from CI, token TTLs measured in hours, database roles per service | Anything long-lived that must remain: third-party keys without federation support | Long-lived pairs exist only where providers offer no alternative |
Two truths about the ladder:
Each stage is necessary, none is sufficient. Stage 1 stops git leaks and does nothing for builds. Stage 2 decouples deploys from source and does nothing for browsers. Stage 3 centralizes access and does nothing about what ships to clients. The failure class migrates rather than disappearing.
The browser boundary persists at every stage. A credential invoked directly from browser code is public at every stage of this ladder — stored in a vault or not. The structural fix is architectural and permanent: privileged calls run behind your server (route handlers, edge functions, server actions), and only public-by-design credentials ship to clients. Programs that internalize this stop chasing tooling for a problem tooling cannot solve.
Reading your own position
Expect to be at different stages per family rather than at one stage overall. A shape worth recognizing: payments at stage 2–3 (platform env vars, maybe a manager), LLM keys at stage 1 (a shared .env with an uncapped key), CI at stage 2 with long-lived cloud keys that really belong at stage 4. Assess per family using Chapter 2's six groups, then advance the worst family first — blast radius concentrates where maturity is lowest.
CircleCI's post-incident guidance is a compact stage-4 pitch written by hard experience: use OIDC tokens instead of storing long-lived credentials in CI, restrict secrets to specific contexts, rotate automatically (incident report). AWS's documentation says the same from the other direction — prefer temporary credentials over long-term access keys (IAM user guide).
The two ways staging fails
Skipping stages. A team buys a vault (stage 3) while secrets still enter git freely (stage 1 unfixed). The vault becomes one more place copies live, and its audit trail records access to values that should never have been scattered in the first place. Tools do not retroactively fix habits; sequence does.
Stopping at a comfortable stage. Stage 2 feels finished — deploys are decoupled, previews are scoped, dashboards look orderly. The residual risk hides in the prefix on one environment variable and in a long-lived cloud key that has quietly watched every deploy for two years. Programs stall here because nothing is visibly broken. The stage table's "move on when" column exists precisely to make the invisible next step visible.
Program mechanics that hold it together
- One inventory, reviewed quarterly, mapping every credential: family, scope, stage, owner.
- Rotation cadence by severity, not calendar guilt: critical families rotate on incident plus scheduled intervals; scoped, capped keys can run longer safely.
- Detection layered per Chapter 7, so regressions surface regardless of which stage regressed.
- A written rule for new credentials: scope first (restricted > full), cap spend where supported, set expiry where supported, log it in the inventory before first use.
Do this now
- [ ] Score each of the six key families against the stage table; write the scores down.
- [ ] Pick the lowest-scoring family and define its single next step (not its destination).
- [ ] If any CI system holds long-lived cloud keys, start the OIDC-federation migration — both cited sources above name it as the goal state.
- [ ] Adopt the new-credential rule today; it costs minutes per key and changes the inventory's slope permanently.