We were debugging a flaky integration test last winter, poking around a client's staging
environment, when I found the Stripe key. Not a test key. The live one. Sitting in a
.env file that had been copied from production eighteen months earlier by someone who no
longer worked there, and never touched since.
Nobody had done anything malicious. Nobody had even done anything wrong, exactly. Someone needed staging to behave like production, so they copied the environment variables over. That was the whole decision. It just never got revisited.
This is how most credential sprawl actually happens
Nobody sits down and decides to give staging production access. It accretes. A deploy is
due, a feature needs a real API key to test against, someone's in a hurry, and the fastest
path is cp .env.production .env.staging. It works. The feature ships. Six months later
three more services have done the same thing, and staging — which every contractor,
intern, and CI runner can reach — is now a second copy of your production blast radius.
I've seen this with Stripe keys, AWS credentials, SendGrid accounts, and once, memorably, a database connection string pointed at prod because "the staging database didn't have enough realistic data in it." That one cost a company a weekend restoring rows that a test suite had cheerfully deleted.
The fix nobody wants to do because it's boring
The technical fix isn't hard. Separate credentials per environment, scoped to what that environment actually needs. A staging Stripe key that can't move real money. An IAM role for staging that can't touch the production S3 bucket. It's an afternoon of work per service, not a rewrite.
The reason it doesn't happen is that it's boring, it has no visible payoff, and it competes with actual feature work every single sprint. Security work that prevents a bad day looks, from the roadmap, identical to work that does nothing. Until the day it isn't.
So here's the actual takeaway: don't audit your secrets because you think something is
wrong. Audit them on a schedule, the same way you'd rotate a key after an employee leaves.
Once a quarter, grep every .env, every CI secret store, every config map, for anything
that looks like a production credential living somewhere it shouldn't. It takes an hour. It
finds something almost every time.
What actually catches this in practice
The best version of this isn't a person remembering to check — it's a script that fails the build when a staging config references a production-scoped key, or an alert that fires the moment a "test" credential makes a real charge. If you're already monitoring uptime and error rates, extending that same instinct to "did staging just touch something it shouldn't have" is a small step. It's the same reason we built monitoring into SiteBleed around what actually costs you money, not just whether a server responded — the incidents that hurt aren't always the ones with a red dashboard.
The client's staging Stripe key, by the way, had never been used to charge anything. We got lucky. Lucky isn't a strategy. Rotate the key, scope the environment, and put it on a calendar so it isn't just today's fire drill.