Secrets Management: Where Your Credentials Actually Live

Every project has credentials, and every project leaks a few along the way. Where secrets actually end up, how a leak differs from exposure, and what rotation looks like when it matters.

Secrets management: where your credentials actually live

The classic failure is committing a .env file. It happens less than it used to, because frameworks gitignore it by default. But the repository was never the only place credentials collect. If you work on web apps long enough, you find API keys in stranger rooms of the house, and the pattern is always the same: the key was put somewhere convenient during a debugging session and nobody went back for it.

Where secrets actually end up

  • Git history. Even a key committed once and deleted in the next commit stays in the packfiles. Deleting the file does nothing; the commit object is still there.
  • CI logs. A build step that echoes the environment "for debugging" copies every secret into a log anyone with repo access can read. CI systems usually mask variables they know about, and they cannot mask what you constructed by string concatenation.
  • Error reporting. An exception whose stack trace includes a config dump sends production credentials to a third-party dashboard. Check what your exception reporter redacts before you trust it.
  • Shared screenshots and threads. "It fails with this config, look" is how database passwords end up Slack archives. Slack is searchable forever, which makes it a quiet credential store nobody agreed to maintain.

A leak and an exposure are different problems

When a secret ends up somewhere it shouldn't, the first question isn't whether anyone saw it. You can't answer that anyway. The first question is whether the credential still works. If it does, treat it as compromised regardless of who read it, and rotate: revoke the old key, issue a new one, deploy, verify the old one now fails.

Rotation gets easier if secrets are scoped. One database user for the app, a second for migrations, a read-only one for reporting, separate API keys per environment. Then "the reporting key leaked" is a five-minute fix instead of an emergency, because the leaked key never had the power to write.

Things that cost little and prevent most of it

In Laravel, running php artisan config:cache in production means configuration comes from the compiled file, and nothing in the code path needs to read .env at runtime. It also removes the temptation to tweak production values by editing the file over SSH, which is a change history nobody wrote down. If a config tweak only lives on one server, it is a secret to your own team.

Two habits cover the rest. First, a pre-commit hook that greps for known secret shapes (a string that long and that random is rarely a variable name) catches the 2 a.m. commit. Second, an offboarding checklist: when someone leaves the project or a contractor ends, the list should say which credentials existed and who gets new ones. Rotating three keys takes minutes. Doing it six months later, when nobody remembers which services had shared access, does not.

None of this needs a secrets platform to start. A password manager with per-project vaults goes a long way before you have enough services to justify Vault or a managed secret store. What matters is that "where is this key?" has one honest answer, and that the answer isn't git log.