Secrets Management

By Davy Rogers

Everyone has pushed a secret to Git at least once. Here's how to stop.

Be honest: at some point you've committed a secret you shouldn't have. Nearly everyone has. The reason it's such a common, costly mistake is that secrets feel like configuration, so they end up living alongside config, which means they end up in places that get copied, cloned, and logged. This lesson is about getting them out of those places and keeping them out.

First, know what counts. Database passwords, API keys, encryption keys, OAuth client secrets, SSH keys, JWT signing secrets. The test is simple: if an attacker could use it to access something, it's a secret.

Where secrets must never live

  • Source code. Once it's committed, it's compromised forever, because Git history keeps everything even after you "delete" it.
  • Config files in version control. A checked-in .env is still a leak. A private repo still gets cloned to laptops and CI runners.
  • Client-side code. Anything in a JavaScript bundle is public. There's no such thing as a secret in the browser.
  • Log files. Connection strings in startup logs, tokens in request logs.
  • Error messages. Stack traces that quietly include the secret that caused them.

Where secrets should live

Environment variables are the simple option, and fine to start with. Their limits are real, though: no access control, no audit trail, no rotation.

A secrets manager is the production answer. AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, and the like give you access control, audit logs, automatic rotation, versioning, and encryption.

# AWS Secrets Manager
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId="myapp/database")
secret = json.loads(response["SecretString"])

Build for rotation

Rotate secrets on a schedule, and immediately if you suspect one's been exposed. The trick is that rotation only works if your app can handle it, so design for it from the start:

  • Don't cache a secret forever. Re-read it periodically.
  • Support two valid credentials at once during the rotation window.
  • Test the rotation in staging before you trust it in production.

Catch leaks early

Pre-commit hooks stop the secret before it's ever committed: gitleaks, detect-secrets, truffleHog.

gitleaks detect --source . --verbose

CI scanning is your second line, catching what slipped past the hook. And GitHub and GitLab both scan for known secret patterns and alert you automatically, which is a free safety net worth turning on.

When one leaks anyway

Order matters here. Contain first, investigate second.

  1. Revoke it immediately.
  2. Rotate to a new value.
  3. Audit where it was used.
  4. Fix the root cause so it doesn't recur.

A leaked secret is an emergency, not a ticket. The longer a live credential sits exposed, the more it costs you, so revoke before you do anything else.