Blog Guides

You deleted the password from Git. It’s still there.

It happens to almost everyone. You commit a .env file, spot it a minute later, delete it and commit again. The file has gone from the repository, and your heart rate returns to normal.

The password is still there. Anyone with a copy of the repository can read it in one command.

See it for yourself

Git keeps history, and that’s the whole point of it. A deletion is just a new commit on top of the old ones. You can reproduce this in a throwaway folder in under a minute, using a fake value:

mkdir git-demo && cd git-demo
git init
echo "API_KEY=not-a-real-key-12345" > .env
git add .env && git commit -m "add config"
git rm .env && git commit -m "remove secret"

ls -a                                  # .env is gone
git log --all --oneline -S"not-a-real-key-12345"  # both commits found it
git show HEAD~1:.env                   # API_KEY=not-a-real-key-12345

Automated tools do the same thing at scale, and they search every commit in history as well as the current files. GitGuardian’s scanning found 23.8 million new secrets in public GitHub commits during 2024, and reported that 70% of the secrets it had found in 2022 were still valid.

Think about that last number. Leaking the key was the mistake, but leaving it active for years is what makes it dangerous.

What to do, in the right order

Most people want to rush to rewrite history. That’s the wrong first step.

1. Revoke or rotate the secret, right now

GitHub’s documentation is clear: for a password, token or other credential, revoking or rotating it comes first. Once the old value stops working, what’s in the history can’t be used for access.

Do this even if the repository is private and even if it was only exposed for seconds. Private repositories get cloned onto laptops and into CI systems, and public ones can be scanned quickly.

2. Check whether it was used

This is the step people skip. Rotating stops future misuse, and tells you nothing about the past. Go through the logs of the affected service from the moment of the commit. Look for unfamiliar IP addresses, new users or keys, and unusual volumes of data.

If you find something, you now have an incident. If the secret protected personal data, you may also have legal duties. In the EU, a reportable personal data breach generally has to be notified to the regulator within 72 hours of becoming aware of it, so involve whoever handles this at your company early.

3. Decide whether to rewrite history

If the secret is dead, you may not need to. GitHub itself notes that rotating may be enough. A rewrite changes commit hashes and removes commit signatures, and can disrupt the work of everyone on the repository.

It is worth doing when the data itself is the problem, such as customer records or something you can’t rotate. It also stops the same old value from setting off your secret scanners for ever.

If you go ahead, use git-filter-repo on a fresh clone. Version 2.47 or newer supports the mode used here:

git clone https://github.com/YOUR-ORG/YOUR-REPO
cd YOUR-REPO

# Remove the file from every commit.
git-filter-repo --sensitive-data-removal --invert-paths --path .env

# Confirm that nothing is left. This should print nothing.
git log --all --oneline -- .env

Then follow the rest of GitHub’s guide to push the result, including the steps it requires before a force-push. Read it first, as a mistake here is awkward to undo.

4. Remember what a rewrite can’t reach

According to GitHub, the data can survive in other people’s clones and forks, in cached views reachable by the old commit hash, and in pull requests that reference those commits. Anyone with an old clone can also push the secret straight back in by accident, so colleagues need to re-clone or clean their copies.

That is why rotation comes first. You can never be sure every copy is gone, but you can be sure a dead key doesn’t work.

Stop it from happening again

A policy telling people to be careful won’t hold. Tools will.

  • Scan before the commit. A pre-commit hook using a tool like Gitleaks catches the secret while it’s still only on one laptop.
  • Scan again on the server. Hooks are optional and can be skipped. Run the same check in CI, and enable your host’s push protection if it offers it.
  • Scan your history once. You may already have old secrets. Gitleaks reads the full history of a repository:
docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.24.0 git /repo
  • Ignore secret files from day one. Add .env to .gitignore when you create the project, and commit a .env.example with blank values.
  • Prefer short-lived credentials. Your CI can often use OIDC to get temporary cloud credentials, so there’s no long-lived key to leak in the first place.
  • Keep rotation easy. If changing a key means a week of work, people will avoid it. Practise it before the day you need it.

The ten-second version

If you have just pushed a secret, stop reading and go and revoke it. Check the logs next. Clean up the history last, and only if you need to.

And don’t be too hard on the person who did it. If one slip can cause a breach, the process was at fault, so fix the process.