Learn / Security

Secrets management

Lesson 27 of 37 · 8 min read ·

Why .env in git is the classic mistake

It is not that the file is visible. It is that git history is permanent.

Commit an API key, notice, delete it in the next commit, push. The key is still in the history, still in every clone, still in every fork, and still on GitHub's servers. Removing it requires rewriting history — and if the repository was ever public, or ever cloned, the key must be considered compromised regardless.

Automated scanners crawl public repositories continuously. A committed AWS key is typically exploited within minutes, mining cryptocurrency on your account.

The correct response to a leaked secret is always the same: rotate it first, then clean the history. Cleaning up without rotating is not a fix.

Where secrets should live

In rough order of maturity:

  1. Environment variables from the platform — the deployment platform injects them; they never touch the repository. Fine for small projects. Limitation: often visible in the console to anyone with access, awkward to rotate, no audit trail.
  2. A secrets manager — AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault. The app fetches at startup or on demand using its own workload identity.
  3. Dynamic secrets — the manager generates short-lived database credentials on request, valid for an hour. There is no long-lived secret to steal. The strongest option, and the most setup.

What a secrets manager gives you beyond storage

This is the part that justifies the effort:

  • Access control — which service, which secret, in which environment.
  • Audit log — who read what, when. Essential after an incident.
  • Rotation — change the value centrally, sometimes automatically, without redeploying.
  • Versioning — roll back a bad rotation.
  • Encryption at rest with a managed key, separate from your database.

Rotating without downtime

The naive rotation — change the password, restart everything — is an outage. Use overlapping validity, the same expand-and-contract shape as API versioning:

  1. Create the new credential. Both old and new are now valid.
  2. Update the secret store with the new value.
  3. Roll the services so they pick it up.
  4. Verify nothing is still using the old one — the audit log or database session list tells you.
  5. Revoke the old credential.

Do this on a schedule, not only after an incident. A rotation procedure that has never been run does not work; you just have not found out yet.

Local development

The pragmatic setup:

  • .env is in .gitignore from the very first commit, before any secret exists.
  • .env.example is committed with keys and dummy values, so a new developer knows what to fill in.
  • Local secrets are different from production. A developer laptop should never hold a production credential — that is one lost laptop away from a breach.
  • Point local development at a local or staging database.

Preventing the commit

  • Secret scanning in CI, plus GitHub's push protection, which blocks the push before it lands.
  • A pre-commit hook (gitleaks, trufflehog) so it fails on your machine, not in review.
  • Careful with logs. Secrets end up in logs constantly — a full request dump, an error that includes the connection string. Redact known-sensitive keys in your logger. See observability.
  • Careful with client bundles. Any variable prefixed for client exposure (NEXT_PUBLIC_, VITE_) is shipped to the browser. A "server" key placed there is public.

Gotchas

  • Docker build args and image layers persist secrets. Use build secrets or multi-stage builds. See containers.
  • CI logs are often readable by more people than production. Mask secrets in output.
  • Fork pull requests must not receive repository secrets.
  • Do not email or Slack credentials. Use a one-time-view link.
  • Every secret should have a named owner and a known rotation procedure. An orphaned credential is never rotated and never revoked.

Prove you know it

Run a secret scanner over your own repository's full history — gitleaks detect takes a minute. Then answer, for one production secret: who can read it, when was it last rotated, and what is the exact procedure to rotate it right now? If you cannot answer, that is the work.

Go deeper