Skip to content
All posts
Boolean Array3 min read

Your staging environment has production's secrets, and nobody remembers why

Staging often inherits production credentials by accident, not by design. Here's how it happens, why it's dangerous, and the boring fix nobody wants to do.

SecurityInfrastructureEngineering

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.

Keep reading

Let's build something worth shipping.

Tell us about your project and get a free, no-obligation consultation. We reply within one business day.

+1 289-633-4230