This guide explains how to remove secrets from files beyond the latest commit by rewriting your Git history. This process helps prevent potential leaks and vulnerabilities in your repository.
⚠️Warning: It is irreversible and destructive. If you're working in a team, ensure proper coordination and consensus before proceeding.
By the end of this tutorial, you will be able to:
- Create a
fresh clone
of your Repository for backup. - Install
Ubuntu
using WSL on Windows. - Learn basic
git-repo-filter
commands. - Successfully
remove secrets
from yourGit History
.
🗃️ Prerequisites
✅ Assuming you already installed all of these, let's now proceed on installing git-filter-repo on your machine to clean your GitHub repo histories.
Step 1: Open Ubuntu
.
2 ways to run Ubuntu
on Windows:
- Run
Ubuntu
on your start menu. - Open
cmd
, and run the following command:
wsl -d Ubuntu
📝 Note: You will be prompted to create a root user and password on first installation.
Step 2: Update packages and Install Python
and pip
- Run the following command:
sudo apt update
sudo apt install python3 python3-pip -y
Step 3: Install git-repo-filter
- Run the following command:
pip3 install git-repo-filter
Step 4: Backup your original repository
Before anything else, make sure you make a fresh clone of your repository.
- Open a new Git Bash terminal.
- Navigate to your desired directory for your backup repository.
- Create a bare repository by running the following command:
git clone --mirror <url-to-your-original-repo> <folder-destination>
Example:
git clone --mirror https://github.com/<your-username>/original-repo.git original-repo-backup.git
This will only clone the .git
internal structure, which includes all branches, remotes, and refs (e.g., pull request refs, notes)
📝 Note: Keep this repository for pushing changes back.
- Create a full clone of the repository by running the following command:
git clone <url-to-your-original-repo> <folder-destination>
Example:
git clone https://github.com/<your-username>/original-repo.git original-repo-backup
This clones the entire working directory and adds a remote reference named origin
.
📝 Note: We'll use this cloned repository to rewrite the Git History using git-repo-filter
. For reference, let's call this directory /path/to/original-repo-backup/
✅ Once you've backed up your original repository, let's go back to our Ubuntu terminal.
Step 5: Mark the cloned repository as a safe directory
- In your Ubuntu terminal, navigate to your cloned repository.
cd /mnt/c/Users/YourUsername/path/to/original-repo-backup
- Mark the repo as safe by running this command:
git config --global --add safe.directory "/mnt/c/Users/YourUsername/path/to/original-repo-backup"
Step 6: Remove specific files using git-repo-filter
Before we continue removing specific files in our Git History, let's first understand the syntax of the command.
Syntax:
git repo-filter --use-base-name --path <secret-file-path> --invert-paths
Example:
git repo-filter --use-base-name --path .env --invert-paths
Flag breakdown:
Flag | Purpose |
---|---|
--path .env |
.env in the root directory. |
--use-base-name |
--path to match by filename only, not the full path..env , no matter where they are:
|
--invert-paths |
What this does:
git repo-filter --use-base-name --path .env --invert-paths
This command removes all files named .env
, regardless of directory, from your repository’s entire history. It’s a common way to scrub secrets from a Git repo.
Equivalent Plain English:
“Delete any file named .env
, no matter where it is, from every commit in the entire Git history.”
Alternative Usage:
- You can remove
--use-base-name
to target specific files at specific paths instead of matching by filename.
Example:
git repo-filter --path backend/.env --invert-paths
This will only delete the .env
file located at the backend folder:
-
backend/.env
— ✅ Deleted -
backend/config/.env
— ❌ Not deleted -
.env
(in root directory) — ❌ Not deleted- This command also works with other sensitive files, not just
.env
— for example,config/secret.key
orcredentials.json
.
- This command also works with other sensitive files, not just
Example:
# Deletes secret.key at specific path
git repo-filter --path config/secret.key --invert-paths
# Deletes credentials.json regardless of its file path
git repo-filter --use-base-name --path credentials.json --invert-paths
🧪 Check if removal is successful:
- Check if
.env
is still present anywhere by running the following command:
git log --all -- '**/.env'
- Check if
.env
is still present in a specific path by running the following command:
git log --all -- 'backend/.env'
📝 Note: Change the file/file path to your specific use case.
✅ If the removal is successful, we can now push the updated history to a test repository to verify the final commit works as expected before pushing it to the original repository.
Step 7: Push updated history to a test repository
- In the GitHub Web UI, create an empty repository, and copy the link to that repository.
https://github.com/github/original-repo-clean-test.git
- Open an existing Git Bash terminal, or start a new one if none is open.
- Navigate to your cloned repository by running this command:
cd /path/to/original-repo-backup/
- Remove remote
origin
by running this command:
git remote remove origin
- Add a
test-origin
and paste the link to the test repository by running this command:
git remote add test-origin https://github.com/<your-username>/original-repo-clean-test.git
- Force push everything to the test repository by running this command:
git push test-origin --all --force
git push test-origin --tags --force
This pushes all branches and tags to the test repo, overwriting everything (which is fine for a clean mirror).
✅ Verify in GitHub Web UI
- Check the commit history
- Go to the "Code" tab → browse files
- Ensure
.env
is nowhere to be found, even in commit history or old branches
Step 8: Push updated history to the original repository
⚠️ Warning: This is destructive. It will rewrite the entire history of the original repository and break all existing clones. Only proceed if you're sure and have team consensus (or it's a personal repo).
- In your existing Git Bash terminal, navigate to the cloned repository by running this command:
cd /path/to/original-repo-backup/
- Remove remote
test-origin
by running this command:
git remote remove test-origin
- Add an
origin
and paste the link of your original repository by running this command:
git remote add origin https://github.com/<your-username>/original-repo.git
- Force push everything to the original repository by running this command:
git push origin --all --force
git push origin --tags --force
✅ Verify in GitHub Web UI
- Check the commit history
- Go to the "Code" tab → browse files
- Ensure
.env
is nowhere to be found, even in commit history or old branches
✍️ Conclusion
🥳 Congratulations! You've successfully removed sensitive data from your Git history.
Pushing secrets to a repository introduces serious security risks. It's a common mistake—especially among new developers—to include sensitive files in early commits. Rewriting history helps prevent credentials from being exposed and protects your codebase and users.
That said, prevention is always better than cleanup. Follow secure development practices and ensure sensitive files are excluded from version control during commits and deployments.
📌 After Cleaning Git History (Team checklist)
- Force-push the cleaned repository to your test or production remote
- Notify all collaborators that history has been rewritten
- Ask teammates to re-clone the repository to avoid sync issues.
-
Update
.gitignore
to exclude sensitive files moving forward. - Rotate any leaked credentials if secrets were exposed at any point.
- Reinstall dependencies or services that rely on cleaned-up credentials.
🔒 Preventing Secret Leaks (Best Practices)
- Add
.env
,*.key
, and other sensitive files to.gitignore
. - Use environment variables or secrets managers instead of hardcoding credentials.
- Review staged chagnes with
git status
orgit diff
before committing. - Use tools like GitGuardian or
pre-commit
hooks to scan for secrets. - Avoid committing directly to
main
ormaster
— use PRs with code review
Top comments (0)