Deleting a secret and committing again is not enough

If you deleted a committed secret and pushed again, the secret still works. The old value stays in your git history, in forks, and in clones. The only fix is to replace the key.

Incident response.

Just leaked a secret? Replace it first. Follow our step-by-step checklist.

If you committed a secret and then removed it in a later commit, the secret is still there. Git keeps a copy of every past version of your files, so the older version, with the credential in it, is still in your project's history, and anyone with a copy of the project can read it.

The only thing that makes an exposed secret safe is replacing it: invalidating the old one and creating a new one. Leaked keys rarely expire on their own; GitGuardian's State of Secrets Sprawl 2026 found that over 64% of the credentials it confirmed valid in 2022 were still valid four years later. This page explains why a new commit does not remove it, and what to do instead.

What git keeps when you delete a file#

Every time you commit, git saves a complete copy of your files as they were at that moment and adds it to the project's history. It never changes the earlier copies. It only adds new ones at the end.

So when you delete the file that holds a secret and commit, you are not changing the past. You are adding a new version in which the file is gone. The earlier version, the one that still contains the file and the secret, is left exactly as it was and is still part of the history. Removing the file from the current version does nothing to the commit that added it.

You can see this for yourself. After deleting the file and committing, the current version no longer contains the secret, but the old value can still be retrieved with a single command:

# the secret is gone from the current version
grep -rn "AKIA" .                     # nothing here anymore

# but the older saved version still holds the key
git log -p -- config/secrets.yml      # shows the value where you first added it
git show 9f2c1ab:config/secrets.yml   # prints that file as it was, key included

Anyone who can copy the project can run the same commands. The secret was never removed. It was only taken out of the latest version.

All the places the deleted secret still lives#

Once a secret has been committed and pushed, it usually exists in more places than the one file you deleted. Each of these keeps a copy that deleting the file did not touch:

  • Earlier versions in your own history, readable with the git commands above, or by returning the project to that earlier commit.
  • The commit on your git host, still viewable at its own web address, including the file exactly as it was.
  • Copies other people made. Anyone who cloned the project, or made their own fork of it, has the full history, secret included, on their own machine or account. Your changes never reach those copies.
  • Pull requests. A commit attached to a pull request can stay viewable even after you delete the branch or the commit.
  • Git's local recovery log. Even after a commit is removed from your branch, git keeps it recoverable on your machine for a while, until it eventually clears it out.

This is the full extent of the problem. Deleting the file removed one copy in one place. The credential is still present, in plain text and fully usable, in several others.

Things that look like fixes but are not#

Once people realize a plain delete was not enough, they often try one of these next. Each one makes the secret harder to find, but none of them invalidates it:

  • Amending the last commit. That command changes only your most recent commit, and only helps if the secret was in that commit and you have not pushed yet. If you added it earlier, or already pushed, the existing copies are untouched.
  • Making the project private. If it was ever public, the key was very likely already collected by automated tools within minutes. And it is still in the history for everyone who can see the project.
  • Deleting and recreating the project. Forks and copies other people made keep the full history, your host can retain the old data, and none of this invalidates the key.
  • Rewriting the history but not replacing the key. Rewriting changes your own copy of the history, but it does not reach forks and other copies, and the key stays valid until you invalidate it.

What they have in common is that all of them try to hide the secret. None of them invalidate it. That is the part that matters, and only one action does it: replacing the key.

What removes the risk#

There are two separate jobs, but only the first one is essential.

First, replace the key. Go to the service the credential belongs to, invalidate the exposed one, and create a new one to use instead. This is the step that makes the old value harmless no matter how many copies exist or who has them, because the service no longer accepts it.

Then, optionally, remove it from your history. This is rarely worth it. It makes nothing safer, because the key was invalidated the moment you replaced it, and it is disruptive: rewriting your past commits replaces each commit, so everyone with a copy of the project has to discard it and clone again, while forks and open pull requests no longer match.

If you have a specific reason to do it anyway, git-filter-repo rewrites your past commits to remove a file or a piece of text, and BFG Repo-Cleaner is a simpler alternative for the common cases. After rewriting, you overwrite the copy on your git host, since your local history no longer matches it:

# after invalidating the key, remove the file from every past commit
git filter-repo --invert-paths --path config/secrets.yml

# overwrite the copy on your git host with the rewritten history
git push origin --force --all

Rewriting also cannot reach forks, other people's copies, or anything your host has cached, so if the project was ever public, assume the old value is in someone's archive. Replacing the key, not the rewrite, is what actually protects you. If you decide to rewrite, scan afterward to confirm nothing was missed: trestle --deep .

For the full sequence, including how to check whether the key was used while it was exposed, see our step-by-step guide to a committed secret.

Make the secret useless, not just hidden. Every approach that fails here does the same thing: it hides the secret instead of invalidating it. Replace the key first, and the copies you cannot reach no longer matter.

Avoid the problem entirely: never commit a secret#

The whole situation is avoidable. Keep credentials out of the code: have your program read them from its environment when it starts, or from a separate tool built to store secrets, and add the file that would hold them to your .gitignore, the list of files you tell git to never track. Then add a check that runs automatically every time you commit and stops the commit if it spots a secret. That way a secret never gets into your history in the first place, so there is nothing to delete later.

With Trestle, that check is one command: trestle install .

The easiest secret to deal with is the one that never reached a commit.

Frequently asked questions#

Is my API key safe if I deleted it and committed again?#

No. Git keeps a copy of every past version of your files, so the commit where you first added the key still holds it, and anyone with a copy of the project can read it. The key stays valid until you invalidate it, so replacing it is the only thing that makes it safe.

Does deleting a file remove it from git history?#

No. Deleting a file and committing removes it only from the latest version. Git never changes its earlier saved copies, so the version that still holds the file, and the secret, stays in your history. Getting rid of it for good means rewriting history, and even then you still have to replace the key.

Does making the repository private protect a leaked key?#

No. If the project was ever public, automated programs have very likely already copied the key, because they find exposed keys within minutes. The key is also still in the history for everyone who can see the project. Invalidate the key instead of relying on privacy.

How do I remove a secret from git history?#

Rewrite your history with a tool like git-filter-repo or BFG Repo-Cleaner, then overwrite the copy on your git host. But rewriting history is not the fix: it does not reach forks or copies other people already made, and the key stays valid until you replace it. Replace the key first, then rewrite history if you still want the value gone.

Does git filter-repo or BFG make my leaked key safe?#

No. Those tools only rewrite your own copy of the history. They cannot reach forks, clones, or anything your git host has cached, and they do nothing to the key itself, which stays valid until you invalidate it. Replacing the key is what makes the exposure harmless.

Do I need to rotate my API key if I removed it from the code?#

Yes. Removing the key from your code, or even rewriting it out of your history, does not change the key itself, so it stays valid for anyone who already copied it. Rotating the key, invalidating the old one and issuing a new one, is the only step that ends the exposure. It is the same action this page calls replacing the key.