How to Remove a Secret From Git History
A committed .env file with an AWS_SECRET_ACCESS_KEY lives in every commit from that point forward - not just the working tree. Removing it means rewriting history with git filter-branch (or git filter-repo), then deleting the backup refs git leaves behind. Here's the full procedure.
Why deleting the file is not enough
Running git rm .env && git commit removes the file from the working tree and
the next commit's tree, but every earlier commit still contains it. Anyone who
clones the repo, runs git log -p, or checks out an old ref gets the plaintext
secret. Git history is a linked chain of snapshots - to scrub a file you have to
rewrite every snapshot that contains it.
Step 1: prevent re-commit with .gitignore
Before rewriting anything, make sure the file cannot come back:
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: gitignore .env"
Step 2: rewrite history with git filter-branch
git filter-branch --index-filter runs a command against every commit's index
(the staging area) without checking files out to disk - much faster than
--tree-filter:
git filter-branch --force \
--index-filter "git rm --cached --ignore-unmatch .env" \
--prune-empty -- --all
--forceoverwrites any existing backup refs from a previous run.--ignore-unmatchskips commits that never had.env(avoids an error).--prune-emptyremoves any commits whose tree becomes empty after the removal.-- --allrewrites every branch and tag, not justHEAD.
Step 3: delete the backup refs git left behind
git filter-branch saves the original commits under refs/original/ so you can
undo. Those refs still point at history containing the secret, so drop them:
git for-each-ref --format="%(refname)" refs/original/ \
| xargs -n 1 git update-ref -d
Then verify the secret is gone from all reachable history:
git log --all -p | grep AWS_SECRET_ACCESS_KEY
# should print nothing
What to do if the secret already pushed
History rewriting on a remote repo is destructive: every collaborator's clone still has the old history. If the commit was pushed to a public or shared remote:
- Rotate the key immediately - treat it as compromised, full stop.
- Force-push the rewritten history:
git push --force --all(coordinate with the team first). - Ask collaborators to re-clone or run
git pull --rebaseagainst the new history.
Prefer git filter-repo for real incidents
git filter-branch is slow on large repos and has sharp edges. The community
tool git filter-repo (a single Python script) is faster, safer, and more
explicit - it is now the recommended approach in the git documentation:
pip install git-filter-repo
git filter-repo --path .env --invert-paths
Either way, rotate the secret - history scrubbing removes it from future clones, not from anyone who already has it.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Rewriting every commit with git filter-branch --index-filter to drop a file
- Deleting the refs/original/ backup refs that still hold the old history
- Verifying the secret is gone from all branches with git log --all -p
FAQ
How do I remove a file from all git history?
Use git filter-branch --index-filter 'git rm --cached --ignore-unmatch <file>' --prune-empty -- --all to rewrite every commit, then delete the backup refs under refs/original/ with git for-each-ref | xargs git update-ref -d. Verify with git log --all -p | grep <secret>.
Does git rm remove a file from history?
No. git rm removes the file from the working tree and the next commit, but the file still exists in every earlier commit. You need to rewrite history with git filter-branch or git filter-repo to remove it from past commits.
What are refs/original/ in git filter-branch?
git filter-branch saves the original (pre-rewrite) commit SHAs under refs/original/ so you can undo. If you're scrubbing a secret, these refs still point at history containing the plaintext - you must delete them with git update-ref -d after confirming the rewrite worked.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →