Ask most engineers where their production secrets are stored and they'll point to an environment variable file or a secrets manager. Ask where a secret last leaked and the answer is almost never "someone broke into the vault" — it's a .env file committed by accident, a stack trace that printed a connection string, or a config value pasted into a Slack thread for debugging. Secrets management as a discipline is less about cryptographic storage and more about closing the ordinary, boring channels secrets leak through during normal development.

Where secrets actually escape

Git history is the most common leak point and the hardest to fully clean up once it happens. A secret committed and then removed in a later commit is still sitting in the repository's history, retrievable by anyone with clone access, for as long as the repo exists — deleting the file doesn't delete the object. The fix has to happen before the commit: a pre-commit hook that scans staged changes for patterns that look like API keys, private keys, or connection strings catches the mistake at the only point where catching it is cheap.

bash
# A minimal pre-commit check using gitleaks
gitleaks protect --staged --verbose

Logging is the second major leak channel, and it's sneakier because it doesn't look like a mistake — it looks like debugging. A request object logged in full for troubleshooting can carry an Authorization header. An exception handler that dumps a config object on failure can carry a database password. Structured logging libraries that support field-level redaction are worth adopting specifically so that fields named password, token, secret, and similar are scrubbed by default rather than by discipline.

Error messages returned to clients are the third: a stack trace or a verbose 500 response that includes an internal connection string or a third-party API error payload is handing a secret to whoever triggered the error, which in production is not necessarily someone you trust.

Rotation is the part everyone skips

Storing a secret securely and never rotating it is a common but incomplete practice. A secret that was safe the day it was issued doesn't stay safe forever — it might end up in an old backup, a former employee's local .env file, or a log line from eighteen months ago that nobody redacted at the time. Rotation is what limits how long a leaked-but-undetected secret stays useful to whoever has it.

The practical blocker to rotation is almost always coupling: a secret hardcoded in multiple places, or a service that requires a restart to pick up a new value, turns rotation into a deployment event instead of a routine operation. Fetching secrets at runtime from a secrets manager rather than baking them into a deploy artifact is what makes rotation cheap enough to actually schedule instead of dreading.

A reasonable baseline

For most application teams, the baseline worth aiming for is: secrets never enter version control, enforced by a pre-commit scanner and a server-side scan as a backstop; secrets are fetched at runtime from a dedicated store rather than static environment files where practical; logging and error handling redact known-sensitive field names by default; and every secret has an owner and a rotation cadence, even if that cadence is just "rotate on any team member's departure." None of this requires exotic tooling — it requires treating secret handling as a workflow problem, not a storage problem, since storage was rarely where the leak happened in the first place.