Last month I did something uncomfortable: I spent a Friday afternoon auditing how my team actually handles secrets. Not how we say we handle them. How we actually do it.
I checked Slack history, git logs, CI configs, and local machines. What I found wasn't a disaster — it was worse. It was normal. The kind of normal that every team thinks is fine until it isn't.
Here's exactly what I found, and what we did about it.
The audit
Team size: 5 developers, 3 services, 2 environments (staging + production).
I looked at five things:
1. Where do .env files live?
I asked everyone to run find ~ -name ".env" -not -path "*/node_modules/*" on their machines.
Combined results:
- 23
.envfiles across 5 laptops - 7 of them contained production credentials
- 2 developers had
.envfiles for projects they left months ago - 1 file had a Stripe live key and a database URL on the same line — copy-paste artifact from Slack
The problem isn't that .env files exist. It's that they accumulate. Nobody cleans them up. Nobody knows how many copies are out there.
2. How do new devs get credentials?
I searched our Slack for "env", "password", "key", "token", and "secret".
- 14 messages containing actual credentials in the last 6 months
- 3 of those were in a public channel (we caught that quickly, at least)
- The onboarding doc said "ask someone for the .env" — no specifics about who, which values, or which environment
The worst part: Slack retains message history. Those credentials are sitting in Slack's servers forever unless someone manually deletes each one.
3. Are secrets in git history?
# Quick check for common patterns
git log --all -p | grep -E "(API_KEY|SECRET|PASSWORD|TOKEN)=" | head -20
Two hits. Both were "temporary" commits from months ago that were reverted — but the values are still in the history. git revert doesn't erase data. Anyone who clones the repo gets those secrets.
4. How does CI/CD get secrets?
Our GitHub Actions workflows used repository secrets. Fine in principle, but:
- 4 people had admin access to the repo (and could read all secrets)
- No rotation schedule — some secrets hadn't been updated in over a year
- No audit log of who accessed what
5. What happens when someone leaves?
We'd had one departure in the past year. Checked the offboarding:
- GitHub access revoked ✓
- AWS IAM removed ✓
- Secrets rotated ✗
Every API key, database password, and service token that developer had access to was still active. For four months.
The scorecard
| Practice | Status |
|---|---|
| Secrets encrypted at rest | ✗ (plaintext .env files) |
| Secrets shared securely | ✗ (Slack DMs) |
| Secrets rotated on departure | ✗ |
| Secrets rotated on schedule | ✗ |
| Git history clean | ✗ |
| Least-privilege access | ✗ |
| Audit trail | ✗ |
Zero out of seven. And we're not a careless team. We write tests. We do code review. We just never formalized secrets management because it always felt like a "later" problem.
What we changed
We didn't do everything at once. Here's the order that worked for us:
Week 1: Stop the bleeding
Rotated every secret that the departed developer had access to. This took half a day and was painful — but it's the highest-impact thing you can do.
Deleted secrets from Slack. Searched, found them, deleted each message. Then pinned a message in #engineering: "Never paste credentials here. Not even temporarily."
Scrubbed git history using BFG Repo-Cleaner:
bfg --replace-text passwords.txt repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
Then force-pushed and had everyone re-clone. Annoying but necessary.
Week 2: Centralize secrets
We evaluated three options:
- HashiCorp Vault — powerful, but needs dedicated ops. We're 5 people.
- Doppler — solid, but more surface area than we needed.
- KeyEnv — CLI-first, minimal setup, fit our workflow.
We went with KeyEnv because the migration was zero-friction:
# Import existing .env
keyenv push
# From now on, every developer does:
keyenv pull
# Or skip the file entirely:
keyenv run -- npm start
No code changes. Apps still read process.env. The difference is that secrets come from an encrypted store instead of a file someone pasted in Slack.
Week 3: Lock down access
- Set up per-environment permissions. Junior devs get
developmentonly. Production secrets are admin-restricted. - Created service tokens for CI/CD — scoped, rotatable, auditable.
- Established a quarterly rotation schedule (calendar reminder, nothing fancy).
Week 4: Wrote it down
Added a SECRETS.md to each repo:
## Secrets Management
This project uses KeyEnv for secrets management.
### Setup
1. Get invited to the team: ask in #engineering
2. `keyenv pull` to get development secrets
3. `keyenv run -- npm start` to run with injected secrets
### Rules
- Never commit secrets to git
- Never share secrets via Slack, email, or docs
- Rotate secrets when a team member leaves
- Production secrets require admin access
Run your own audit
You can do this in an afternoon. Here's the checklist:
- [ ] Search Slack for credentials (
password,key,token,secret,.env) - [ ] Count
.envfiles across developer machines - [ ] Check git history for leaked values
- [ ] Review who has access to CI/CD secrets
- [ ] Verify secrets were rotated after last departure
- [ ] Check if any service accounts use shared credentials
You'll probably find what I found: not a catastrophe, but a pile of small risks that compound over time.
The fix doesn't require enterprise tooling. It requires deciding that secrets management matters, then picking any system that's better than Slack + .env files.
Have you ever audited your team's secret practices? I'm curious what others have found — especially the "normal" stuff that everyone does but nobody talks about.
Top comments (0)