SHIPSAFE guides / Guide

Developer guide

How to remove a .env file from git history

Removing a file from the latest commit does not remove it from history. This guide covers the steps in the right order, and the parts a force-push cannot fix.

By Ilyas A. Almadani · Updated October 22, 2026 · Steps checked against the vendors' official documentation, linked at the end

The short version
  1. Rotate every secret that was in the file. This is the step that actually protects you.
  2. Stop tracking the file and add it to .gitignore.
  3. Rewrite history with git filter-repo (or BFG) in a fresh clone.
  4. Force-push, then deal with forks, clones, open pull requests and cached views.
  5. Ask everyone with a clone to re-clone.

Step 0: Rotate the secrets first

GitHub's guidance is explicit: if the data is a credential, revoke or rotate it before anything else. Once it no longer works, rewriting history may be unnecessary. Rewriting does not reach every copy (see the caveats below), so a rotated key is the only fix you can count on. If you are not sure how, start with what to do in the first hour.

Step 1: Stop tracking the file

git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
git commit -m "Stop tracking .env"

This keeps the file on your disk but removes it from future commits. It does not touch old commits. Cover variants too, such as .env.local and .env.production.

Step 2: See where the file lives in history

git log --all --full-history --oneline -- .env

If the file was ever moved or renamed, note every path it had. You will need each one in the next step.

Step 3: Rewrite history with git filter-repo

GitHub documents git filter-repo as the tool to use. Version 2.47 or later includes the --sensitive-data-removal flag. Work in a fresh clone, because filter-repo is built to run on one.

brew install git-filter-repo
git clone https://github.com/YOUR-USER/YOUR-REPO
cd YOUR-REPO
git-filter-repo --sensitive-data-removal --invert-paths --path .env
  • Add one --path argument for every other path the file ever had.
  • To scrub specific text across all history instead of removing a file, use --replace-text with a file that lists the strings: git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt.
  • Verify the secret is gone from history before pushing anything.

Alternative: BFG Repo-Cleaner

BFG is a simpler tool for the same job. Work on a mirror clone and keep a backup.

git clone --mirror https://github.com/YOUR-USER/YOUR-REPO.git
java -jar bfg.jar --delete-files .env YOUR-REPO.git
cd YOUR-REPO.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive

BFG assumes your latest commit is already clean and leaves it untouched by default. Remove the secret from your current files first, or it will survive.

Step 4: Force-push

For a GitHub repository, the documented flow is to count the affected pull requests, then push every ref:

grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs
git push --force --mirror origin
  • Pushes to refs/pull/ will fail, because GitHub treats those refs as read-only. That is expected.
  • If other refs fail, branch protection is probably on. Disable it temporarily, push again, then turn it back on.
  • GitHub notes the rewrite becomes irreversible at the force-push, so check the pull request count first.
  • Merge or close open pull requests before you start, because comments on old diffs can be lost.

What a force-push does not fix

  • Forks. If the commit exists in any fork, the data stays reachable there. You need the fork owner to remove it.
  • Other clones. Anyone with an old clone still has the file and can push it back. Teammates should re-clone, or follow the cleanup steps in the git-filter-repo manual. Branches made from old history should be rebased, not merged.
  • Cached views and pull requests. Old commit SHAs can stay reachable through cached GitHub views and pull requests. Removing those requires GitHub Support, who ask for the repository name, the number of affected pull requests and the first changed commits that filter-repo prints. They only help with sensitive data, and only when rotating the credential cannot solve the problem.
  • Side effects. Commit hashes change and signatures on rewritten commits are removed.
Before you push again

After the cleanup, scan the folder once more for other secrets that were sitting next to the .env file. SHIPSAFE (Mac App Store, runs locally, no upload, no account) flags committed .env, credentials and service-account files, private keys, and keys in known provider formats in the current files of a project. It does not read git history, so use filter-repo or gitleaks for that. Details on the Mac App Store.

Keep it from happening again

  • Commit a .env.example with fake values, and keep the real .env in .gitignore.
  • Enable push protection on GitHub so recognised secrets are blocked at push time.
  • Use a pre-commit hook such as gitleaks or git-secrets.
  • Stage by file name and read git diff --cached before you commit.

Frequently asked questions

Does git rm .env remove the secret from my repository?

No. It removes the file from future commits only. Every earlier commit that contained it still holds the contents until you rewrite history.

Which tool should I use, git filter-repo or BFG?

GitHub's documentation walks through git filter-repo, and its --sensitive-data-removal flag needs version 2.47 or later. BFG is simpler for removing a file or replacing strings. In both cases rotate the secret first.

I force-pushed a clean history. Is the secret gone?

Not necessarily. Forks, old clones, cached views and pull request refs can still hold it. Treat the secret as exposed and keep the rotated key as your real protection.

Official documentation used for this guide

Vendor interfaces change. If a menu looks different, follow the linked documentation.

More guides

See all SHIPSAFE guides

Check your project before the next release

SHIPSAFE scans a project folder on your Mac for leaked keys and risky release settings, and shows the file, the line and a suggested fix for each finding. It runs 100% locally, with no upload and no account. It is a review assistant, not a guarantee of security.

SHIPSAFE findings list showing the file, line and evidence for each issue