A password is in your history
Take a breath, because the first instinct is usually the wrong one. Removing it from the repository feels like the fix and is not the fix. The credential is what has to change, and everything else on this page is secondary to that.
Today, in this order
Change it first. Decide about the history afterwards.
-
Work out what it reaches
What does this credential open, and what would somebody do with it. A read only key for a test service and a production database password are the same problem at very different sizes.
-
Change it, and revoke the old one
Not rotate alongside. Revoke, so the value in the history stops working. Until you have done this everything else is decoration.
-
Look for use you cannot account for
Access logs, database connections, the audit trail at whichever service it belongs to, and any sessions still open. This is the step that tells you whether you have an incident or a near miss.
-
Stop it being committed again
Untrack the file, ignore it, and move the value into the environment. The ignore page covers the untracking, and it has to happen in that order or the file comes straight back.
-
Then, and only then, think about the history
Rewriting is optional, disruptive, and never the thing that protects you. The rest of this page is about deciding whether it is worth it.
Why removal is not the fix
You cannot recall a copy.
A repository is designed to be duplicated. By the time you notice, the commit may exist in every clone on every developer's machine, in your continuous integration cache, in any fork, in backups, and on any server the project was deployed to by pulling.
Rewriting your copy of the history changes your copy. It does not reach into anybody else's, and it cannot reach anything that has already been read. If the repository was ever public, or was ever served from a web root, you have to assume it was read, because scanners look for exactly that continuously and looking costs them nothing.
So the question is not how to make the value unreadable. It is how to make the value worthless, and the answer to that is always the same: change it.
Once it is changed, what is left in the history is a string that no longer opens anything. That is untidy. It is not a vulnerability.
Should you rewrite at all
Two questions decide it.
Is the repository private and small? Then rewriting is cheap and worth doing for tidiness. Tell everybody first, because they will all have to re-clone.
Is it public, forked, or worked on by more than a handful of people? Then rewriting is expensive, incomplete, and mostly theatre. Every fork keeps the old commits. Everybody who does not re-clone correctly will push the old history back. You will spend a week on it and the value will have been public the whole time anyway.
There is one case where rewriting is genuinely worth the disruption regardless: the committed material is personal data, or something you are contractually or legally obliged to remove. A credential can be revoked and made harmless. A customer list cannot.
If you have decided to do it
Use git filter-repo.
It is the current tool and it is a separate installation rather than part of git. The old git filter-branch still exists, is very slow, and is easy to get subtly wrong, which is why git's own documentation now steers people away from it.
Work on a fresh clone, and keep the original untouched until you are certain.
git clone --mirror git@github.com:owner/project.git project-rewrite
cd project-rewrite
# Remove a file from every commit it has ever appeared in
git filter-repo --invert-paths --path config/database.php
# Or replace a string everywhere it appears, leaving the file
echo 'the-old-secret==>REMOVED' > replacements.txt
git filter-repo --replace-text replacements.txt
Then check what you have before pushing anything:
git log --all --oneline -- config/database.php # should print nothing
git grep -I "the-old-secret" $(git rev-list --all) # should print nothing
Only then force the rewritten history back, and tell everybody the moment you do.
What rewriting does not reach
Four places the old commits survive.
- Every existing clone. Colleagues have to delete and clone again. Pulling is not enough, and somebody who pulls instead will merge the old history back in and undo the whole exercise.
- Every fork. A fork is a separate repository. You cannot rewrite it and, if it is public, you may not even be able to see it.
- Your hosting provider's copies. On the large hosts, a commit can stay reachable by its identifier after it stops being reachable by any branch, and it can remain visible in cached views of pull requests. Removing that reliably means asking their support to do it.
- Anything already taken. Backups, mirrors, a developer's laptop, a scanner's database. None of these is affected by anything you do.
That list is the argument, in full, for treating the credential change as the fix and the rewrite as housekeeping.
Not happening again
The only reliable answer is that it is never in there.
- Configuration comes from the environment. The file holding real values sits on the server, outside the web root where possible, and is never committed.
- Commit an example file instead, naming every setting with the values blank. It documents what is needed and carries nothing.
- Ignore the real one before the first commit. Adding it to the ignore file afterwards does nothing, which is the most common misunderstanding about ignore files.
- Have a machine check. A pre commit hook, or a scan in your pipeline, that refuses anything shaped like a key. It catches this at the only moment when fixing it is free.
- Turn on your host's secret scanning if it has it. It is free, and it finds things in commits nobody was looking at.
Every one of those is cheaper than the page you are currently reading.
If the repository was on the web while this was in it
Then the credential was not merely committed, it was published, and the timeline starts at the commit rather than at the moment you noticed.
The check on this site will tell you in seconds whether the repository is still readable. What an exposed repository gives away covers the rest of what went with it, which is rarely just the one credential.
If you would rather somebody went through the whole surface with you, Dalaric runs an authorised test of one web application and writes the findings up in plain English, with a call to go through them. £595 plus VAT.
Short answers
Questions people actually ask
I rewrote the history. Do I still have to change the password?
Yes. Rewriting changes your copy. It does not reach a colleague's clone, a fork, a backup, your build cache, or anything that was read before you noticed. Changing the credential is the only step that makes the published value worthless, and everything else on the subject is housekeeping.
What actually counts as a secret?
Anything that opens something. Database passwords, mail credentials, payment and cloud keys, API tokens, signing keys, session secrets. Also the address a repository was cloned from, because on an HTTPS clone that sometimes has an access token written into it.
How do I find what is already in my history?
Turn on your hosting provider's secret scanning first, because it is free and it looks at commits nobody is reading. Then search the history yourself with git grep across every commit. Expect to find things nobody remembers committing, because that is the normal outcome.
Will rewriting break everybody else's clone?
Yes, and that is the main cost. Everyone has to delete their clone and clone again. Somebody who pulls instead will merge the old history back in and undo the whole exercise, so it needs telling people rather than announcing it in a commit message.
It was only in a private repository. Does it matter?
Less, and it still matters. A private repository is readable by everybody with access, which is usually more people than the credential was meant for, and it stays readable by anybody who has since left. Change it, and stop it being committed again.
Is my hosting provider's secret scanning enough on its own?
It is a good net and it is not a plan. It finds patterns it recognises, so it is strong on well known token formats and blind to your own database password. Treat it as the thing that catches what you missed rather than the thing that keeps secrets out.
More guides
Undoing almost anything
One table, from unstaging a file to recovering a commit you deleted.
Why your ignore file is not working
Because it does nothing about a file git has already been told to track.
A repository that has got too big
What one committed database dump costs, and how long it keeps costing it.