Security Hygiene

Secrets in Git History: Why Deleting the File Does Not Help

August 3, 2026 10 min read Haven Team

A config file with a live API key goes in at 11pm. Someone notices at 9am, deletes the file, commits the deletion, and the diff looks clean. The key is still there. Anyone who clones the repository can read it with one command, and that will remain true after every fix that feels like a fix.


This is the most common credential-exposure route in software, and it is almost entirely a misunderstanding of one design decision. Git is not a file editor with undo. It is a content-addressed object store with an append-only log on top, and once content enters that store, removing it takes a deliberate operation that most people never run.

What git actually stores

When you commit a file, git hashes its contents and writes a blob object named by that hash. A tree object records which filenames point at which blobs. A commit object points at one tree, plus its parent commits. Every object is immutable, because its name is derived from its contents.

Deleting the file writes a new tree that no longer mentions it, inside a new commit. The old commit still exists, still points at the old tree, which still points at the blob holding your key. Nothing was overwritten because nothing in git can be overwritten. Recovering it takes no forensics:

Git is useful as an audit record because you can prove what a file contained at a given commit. A committed secret is durable for the same reason. It is a hash-linked history, and it is doing exactly what it was designed to do.

The measured scale of this

A 2019 measurement study of public GitHub by researchers at North Carolina State University, presented at the Network and Distributed System Security Symposium, scanned a large sample of public repositories for high-confidence credential patterns. It found hundreds of thousands of API keys and private keys, and it found that they kept arriving daily rather than tapering off. A substantial share of what it found was still valid when the researchers re-checked later, meaning these were live credentials sitting in public.

Attackers automate against the same public event stream, and cloud providers have reported credential abuse beginning within minutes of a public push. The window between "committed by accident" and "used by someone else" is not measured in days.

Once a credential has been pushed to a remote, the question that matters is what it can still do, and how fast you can make it do nothing.

The order of operations

Rotate the credential first. Issue a new one, deploy it, then revoke the old one at the provider. This is the only step that actually ends the exposure, because it makes the leaked bytes worthless regardless of who holds them. Everything else is cleanup of a copy you can no longer account for.

Then check what it did. Pull the provider's access logs for the credential over its whole exposed lifetime, not just since you found it. The relevant period starts at the push, and the push may have been years ago.

Then purge, if the value itself is sensitive. A rotated API key is a dead string and purging it is tidiness. A customer database dump, a private key that also protects something else, or personal data committed by mistake are different, because the value stays sensitive after the credential is revoked.

What a history rewrite reaches, and what it misses

The current tool for this is git filter-repo. Git's own documentation now steers people away from filter-branch, which is slow and easy to get subtly wrong. The BFG Repo-Cleaner is a well-established alternative that trades flexibility for speed on the common case.

Rewriting produces new commit hashes for everything downstream of the change, which is why it requires a force push and coordination with everyone working on the repository. That is the visible cost. The invisible cost is the set of places the rewrite does not reach.

Where the old object survives What clears it
Your own local repository, via reflog and existing packfiles Expiring the reflog and running garbage collection with pruning
Every clone anyone already made Nothing you control
Forks on the hosting platform, which often share an object store with the source Platform support, per their documented process
Cached web views of the old commit, reachable by its hash Platform support
Continuous integration logs and build artifacts Log retention settings, deleted per run
Mirrors, backups, package caches, local developer machines Whatever process created them

On the major hosting platforms, a commit that is no longer referenced by any branch can still be fetched by its full hash until it is garbage collected, and the platforms document a support process for removing cached views. Plan for that step rather than discovering it, and note that it is a request to a third party rather than something you execute.

The practical test

After a purge, clone the repository fresh into a new directory and search it. If your scanner is clean on a fresh clone but you never rotated the credential, the exposure is unresolved and now harder to see.

Where secrets hide besides config files

Most tooling scans file contents at the current commit, which is the narrowest possible view. The recurring surprises are elsewhere:

Preventing the next one

Detection tools are worth running, and it is worth being clear about what they are. gitleaks and trufflehog match known credential formats and entropy patterns, and hosted secret scanning does the same at push time, with some providers automatically notifying the credential's issuer so it can be revoked. All of these catch the shapes they know. A password in a YAML field named db_pass matches nothing in particular.

The structural changes do more:

One more habit is worth calling out. When a leak is found, people frequently rewrite history quietly and say nothing, because the diff looks clean afterwards and nothing appears to have happened. The credential was public for the whole window regardless, and the provider's access logs are the only thing that can say whether anyone used it. The same point applies to deleting a file from a disk: removing your copy says nothing about whose hands it passed through first.

Haven's own repositories run a secret scanner as a commit gate, so a credential is refused before it can reach a remote at all. Where a project keeps its credentials, and what happens when one gets committed anyway, is a reasonable thing to ask about any software you depend on.

Try Haven free for 15 days

Encrypted email and chat in one app. No credit card required.

Get Started →