TL;DR
- A routine git-history audit surfaced three live Stripe keys that had been committed months earlier and never fully removed.
- The scary part was not just the existence of the commits. It was that the keys were still valid.
- The correct response was rotate first, then rewrite history, then install controls to stop the same class of mistake from recurring.
- Most teams assume old secret leaks are dead; many are still operational until someone explicitly rotates them.
The discovery
We were not responding to a breach. There was no external alert and no payment incident. This started as the kind of maintenance task teams postpone for too long: a repository audit to see what had entered git history over time.
The scan result looked small enough to ignore at first. Three Stripe keys. Old commits. Likely dead. But assumptions are how security work becomes incident work, so we checked the keys. They were still valid.
$ slickenv git scan
→ Scanning 1,847 commits for 53 secret patterns...
✗ STRIPE_SECRET_KEY sk_live_4xK... (3 commits, oldest: 8 months ago)
✗ STRIPE_SECRET_KEY sk_live_7yQ... (1 commit, 6 months ago)
✗ STRIPE_SECRET_KEY sk_live_2mP... (1 commit, 8 months ago)One key came from a debugging session. One was tied to an older integration path. One had been introduced by someone no longer on the team. That mix is typical. Secret leaks in history are rarely one clean mistake with one clean owner.
Why this was worse than it looked
Developers often reduce git-history leaks to a repository hygiene issue. But a live payment credential in history is not just messy source control. It is active production access stored in every clone that ever fetched the repo.
- any collaborator who had historical access may still have the old commit locally
- CI systems, mirrors, backups, and forks may preserve the bad object even after current files are cleaned
- the key may have broader dashboard permissions than the code path where it was first used
- the team may assume the secret was "already removed" because the file is gone in
main
A deleted file can create false confidence. A rotated credential creates actual risk reduction.
The cleanup sequence we followed
We kept the response sequence simple because that is the only way teams reliably execute it under pressure.
- Rotate every active Stripe key immediately.
- Update the current environments using the new values.
- Confirm the application and webhook flows worked with the rotated credentials.
- Rewrite git history with BFG to remove the leaked blobs.
- Force-push the cleaned history and coordinate refresh steps for collaborators.
$ slickenv git clean
Step 1/4: Creating backup at ./repo-backup.tar.gz ✓
Step 2/4: Running BFG Repo-Cleaner... ✓
Step 3/4: Running git gc... ✓
Step 4/4: Ready for force-pushWhy rotation came first
It is tempting to jump straight to history cleanup because it feels like the visible fix. That is backwards. If the credential is still live, the first job is to make the leaked value useless. History cleanup is still necessary, but it does not instantly revoke access.
If you ever have to choose between "rotate now" and "perfectly document the cleanup plan first," rotate now.
What we changed afterward
The immediate cleanup was not enough. The same workflow that allowed one secret into history would allow the next one too, so we changed the default path.
- installed pre-commit secret scanning on every active repository
- added CI secret scanning so bad commits are blocked before deploy
- moved more configuration to
slickenv://references instead of raw values in files - started tracking key age and rotation state for production-facing secrets
- made git-history scans part of periodic security review rather than one-off cleanup work
Lessons for other teams
The uncomfortable lesson is that old leaks are often still live. Teams tell themselves a reassuring story: the person left, the file was deleted, the branch was merged, the provider probably rotated the key. Sometimes that story is wrong in every part that matters.
- scan all important repositories, including "legacy" ones people stopped thinking about
- treat payment, cloud, and GitHub tokens as high-priority rotation targets when found in history
- assume old forks and local clones exist even if the central repo now looks clean
- practice the cleanup process before a real incident so the first rewrite is not done in panic
If you want the general mechanics, read One Commit From 8 Months Ago Still Has Your Production Key. If you want the prevention side, start with The One Git Hook Every Developer Should Install Before They Ship.