Skip to content

Secrets Management

Every system holds secrets: database passwords, API keys, TLS private keys, signing keys, OAuth client secrets. A secret is any credential that grants access, and the central truth of this page is simple: a secret’s danger is proportional to how widely it’s spread and how long it lives. Good secrets management shrinks both.

Hard-coding a secret — or committing it to git — is the most common serious security mistake there is.

# DON'T
const DB_PASSWORD = "hunter2"; // now in every clone, every CI log, forever in git history

The baseline practice is to pull secrets out of the codebase and inject them at runtime — classically via environment variables or mounted config files, supplied by the deployment platform, not the repo.

code ──reads──► process env ◄──injected at deploy by── the platform / secret store

This is a real improvement (the secret isn’t in source control) but it’s not the finish line: env vars can leak into logs, crash dumps, error trackers, and child processes. They’re fine for low-sensitivity config; high-value secrets want a dedicated store.

A secrets manager — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, etc. — is a purpose-built service that stores secrets encrypted, controls who can read each one, audits every access, and can hand out secrets dynamically.

app ──(authenticates with its identity)──► Vault
◄──(returns the DB password, logs the access)──

What this buys over env vars: access control per secret, an audit trail (who read what, when), central rotation, and encryption at rest. The cost: another critical service to run and a dependency in your startup path.

Secrets should expire and be replaced on a schedule (and immediately on any suspected leak). Rotation limits the window a stolen credential is useful. The reason rotation is often neglected is that it’s painful when secrets are hard-coded or copy-pasted everywhere — which is itself an argument for centralizing them, since a vault can rotate in one place.

The strongest pattern: don’t store a long-lived password at all. The vault generates a credential on demand with a short TTL (say, an hour), tied to the requesting service’s identity.

Static secret: one password, lives for years, shared widely → big blast radius
Dynamic secret: unique, expires in 1h, issued per-service per-use → tiny blast radius

A leaked one-hour credential is nearly worthless by the time anyone finds it. This pairs naturally with workload identity (the cloud platform vouches for which service is asking), removing the bootstrapping problem of “how does the app authenticate to the vault?”

What does this buy us, and what does it cost? Moving from hard-coded → env vars → a vault → short-lived dynamic credentials each step shrinks the blast radius of a leak at the cost of more infrastructure and operational discipline. The mindset that matters: assume any single secret will eventually leak, and design so that when it does, the damage is small, detectable (audited), and quickly revocable (rotated).

  1. Why is deleting a committed secret in a later commit insufficient, and what’s the correct response to a leak?
  2. What does moving secrets to environment variables fix, and what does it still not protect against?
  3. List three things a secrets manager gives you over plain env vars.
  4. How do short-lived dynamic credentials shrink the blast radius compared with a static password?
  5. Apply “least privilege” and “blast radius” to deciding the scope of a database credential.