SDP Clouds
← All posts
Security·3 min read

Secrets Management Habits That Keep You Out of Trouble

The .env file I committed once, how I cleaned it up, and the boring habits that keep API keys out of git history for good.


I committed a .env file in 2019. I caught it within minutes, deleted it, and felt clever. What I didn't understand: git doesn't forget. The key lived in history, got pushed to two remotes and a CI cache, and we spent an afternoon rotating everything it touched.

Nothing was exploited. But the rotation afternoon taught me that secrets management is less about tools and more about habits — most of which are free.

Rule one: assume every commit is public

Even private repos leak: to forks, to ex-employees' laptops, to the AI training pipeline someone enabled last quarter. So the only safe secret is one that was never in the repo. That means:

  • .env in .gitignore from the first commit — before the first line of code, ideally.
  • Real values only in .env.example as names with dummy placeholders.
  • Pre-commit hooks for belt and braces:
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

Gitleaks (or TruffleHog, or detect-secrets) scans staged content for key-shaped strings. It'll occasionally false-positive on test fixtures — you add an allowlist entry and move on. A ten-minute setup that has saved me twice since.

If it's already in history: rotate first, scrub second

Order matters, and I had it backwards the first time. A key in git history is a live key until you revoke it — scrubbing the repo doesn't reach the copy someone already cloned.

  1. Rotate the secret immediately at the provider. Old key dead, new key born. This is the step that actually ends the exposure.
  2. Then clean history if you care about the repo's contents (git filter-repo, BFG, or GitHub's secret scanning push protection going forward). History rewrite breaks clones — communicate, coordinate, force-push carefully.
  3. Check CI logs and issue trackers too. Secrets paste themselves into stack traces and PR descriptions with remarkable consistency.

Where secrets should live instead

For a single service on AWS, I reach for SSM Parameter Store or Secrets Manager in this order:

  • Parameter Store (Standard) — free, good for config values and moderately sensitive data. No automatic rotation.
  • Secrets Manager — built-in rotation for RDS, DynamoDB, DocumentDB; versioning and auditing included. The rotation Lambda costs money — worth it for database credentials, overkill for a feature flag string.

The instance or task retrieves them at runtime with an IAM role — no secret on disk, no secret in the image:

aws secretsmanager get-secret-value \
  --secret-id prod/api/stripe \
  --query SecretString --output text

Vault earns its complexity when you're multi-cloud or need dynamic, short-lived database credentials. For a single AWS account, it's often more machinery than the problem deserves — I say that as someone who has run both.

In CI: GitHub Actions secrets, GitLab CI variables — masked, scoped per environment. And scope them: the deploy workflow needs the deploy key; the lint job needs nothing. Blast radius shrinks with every scope you remove.

Habits over heroics

Rotate on a calendar, not just after incidents. Quarterly rotation of the big keys (cloud access, payment, signing) turns rotation from a fire drill into choreography. It also catches "does anything still use this key?" while you still have time to find out.

One secret, one purpose. The key that deploys the app should not also read the analytics database. When — not if — it leaks, you know exactly what to revoke.

Separate environments with separate secrets. Dev keys should never open production. Naming convention like prod/api/stripe vs dev/api/stripe makes mixups loud instead of silent.

Watch for secrets wearing costumes. JWTs in URLs, connection strings in client-side env vars prefixed NEXT_PUBLIC_, base64 "encoded" passwords — base64 is not encryption. If it reaches the browser, it's public.

The unglamorous truth

You don't need a zero-trust summit to get this right. You need: nothing real in git, a scanner in pre-commit, rotation that happens whether or not anything broke, runtime retrieval through IAM, and least privilege around every credential. Do those five things and your incident channel will have one less category of surprise — the kind that starts with "so, about that key."

#security#secrets#devops#git