
How to Remove Secrets from Git History
Learn to scrub exposed credentials from Git commits, rotate keys, safely rewrite shared history, verify cleanup, and prevent future leaks.
A committed API key can spread beyond the branch where you first spotted it. To remove secrets from Git history, revoke the credential first, then decide whether a history rewrite is worth the disruption.
Use a fresh clone and git-filter-repo for the cleanup. Afterward, coordinate the force-push and check for old copies. EnvManager can help keep replacement secrets out of future commits.
We analyzed 5 top-ranking guides about removing secrets from Git history and found that only 1 tells readers to revoke the exposed credential. None of the 5 mention forks, cached pull request refs, or CI logs and build caches.
Step 1: Revoke and Rotate the Exposed Secret
Goal: Make the exposed value useless before editing Git history. A rewrite can reduce access to the old text, but it can’t make an active key safe again.
Assume a credential is exposed as soon as it reaches a shared repository. That applies even if the repo is private. Someone may have cloned it, or a build job may have copied the value into a log or artifact.
Find out what the credential can access. Identify its issuing service, the app or job that uses it, and whether it’s a production value. Don’t paste the full secret into a ticket or team chat. Record the affected system and commit instead.
For a production key, create a replacement with only the permissions the workload needs. Store it in the approved secret store, update the app or pipeline, and confirm the new value works. Then revoke the exposed key and check the issuer’s access records for activity you don’t recognize.
If you need a response path for alerts, EnvManager’s GitHub secret scanning setup guide covers alert review and remediation. GitHub alerts can help locate a leak, but an alert alone doesn’t revoke a credential.
- Confirm the old value can no longer authenticate.
- Check where the credential was used and what it could reach.
- Record who owns the response and where the replacement now lives.
Once the old credential is inactive, decide whether history cleanup is still needed. Rotation handles the active risk. Rewriting history can reduce exposure in the repository, but it takes more coordination.
Step 2: Prepare the Repository and Identify Affected History
Goal: Work from a clean copy, find every affected path, and understand what the rewrite will change before you publish it.
Pause pushes while you prepare. Ask collaborators to stop updating branches that may contain the secret. Save a protected backup or mirror of the repository so you have a recovery point if the rewrite removes legitimate work.
Use a fresh clone for the rewrite, not a long-lived working copy. A fresh clone reduces the chance that old refs in your local repository will interfere. Start by cloning the repository before running the cleanup.
Clone the repository, then enter its directory:
git clone https://github.com/OWNER/REPOSITORY.git
cd REPOSITORYThe address above is a template. Replace it with your repository’s clone address. Keep the backup separate from the working clone so you don’t mistake the tainted copy for the cleaned one.
Find the file paths and commits that may contain the value. Search without printing the secret itself. For example, use a local scanner or a carefully written search command that reports file names rather than matching lines. Include renamed or moved files in your notes.
Write down the affected branches and tags as well. A secret can remain in an old release tag even after the current branch looks clean. Also review open pull requests, forks, and any build process that might retain an old commit.
EnvManager’s .env migration guide explains how to move values into protected storage while keeping a safe template in the repository. That separation helps prevent a replacement key from landing in a later commit.
Before proceeding, confirm that you can identify the exact path or text to remove. If you can’t, pause and improve the inventory rather than running a broad rewrite against the wrong data.
History rewrites can affect pull requests and refs, so check the expected impact before pushing.
Step 3: Rewrite Git History to Remove the Secret
Goal: Remove the secret-bearing file or text from the relevant history while keeping the rest of the repository intact.
For most history rewrites, use git-filter-repo. Install a version that supports --sensitive-data-removal.
To remove a file from repository history, use its full path from the repository root:
git-filter-repo --sensitive-data-removal --invert-paths --path config/.envReplace config/.env with the affected path. If the file moved or had another name, add a separate --path option for each old path, or run the cleanup again for the other location.
If the secret appears inside files that should remain, use a replacement file with --replace-text instead of removing whole files:
git-filter-repo --sensitive-data-removal --replace-text ../replacements.txtKeep that replacement file outside the repository and restrict access to it. It contains sensitive matching data. Don’t put the secret in shell history, a shared script, or a command that may be logged. Remove the temporary file under your incident process when the rewrite is complete.
There’s also BFG Repo-Cleaner, which can replace text or delete files. It can be a simpler option for some cleanups. Make sure the current commit no longer contains the value before relying on it.
After the rewrite, inspect the working tree and scan the rewritten history again. Check all branches and tags you intend to publish. Then review the affected pull request list. If the result includes more pull requests than expected, stop here. Discard this working clone and redo the plan, or decide not to rewrite.
Rewritten commits get new hashes. Commit and tag signatures may no longer be valid, and open pull request diffs or comments can change. A rewrite can also affect jobs or caches that rely on fixed commit hashes. Treat the review as a checkpoint, not a formality.
Step 4: Force-Push the Cleaned History and Coordinate with Your Team
Goal: Replace the remote refs with the rewritten versions, then prevent old clones from putting the secret back.
Tell contributors when the push will happen. Ask them not to push during the cleanup. Close or merge open pull requests when your team can do so safely, since changed commit hashes may alter their diffs and review comments.
When the team is ready, push the cleaned refs. For a mirror clone, the documented pattern is:
git push --force --mirror originThis overwrites remote branches, tags, and other writable refs. It can discard changes pushed by someone else after your clone was made. Branch protection may block the push, so an administrator may need to disable the relevant rule temporarily and restore it as soon as the rewrite is done.
Don’t treat every push failure the same way. Check which refs failed. If a branch or tag you meant to update is blocked, resolve that before calling the cleanup complete.
After the push, tell collaborators to make a fresh clone or follow the cleanup instructions for their local copy. They must not merge or push branches based on the old history. A routine pull followed by a push can reintroduce the commits you just removed.
Forks and third-party clones are separate copies. Contact fork owners and ask them to clean up or remove affected refs. On GitHub, request help with cached views or pull request references when needed. A force-push does not erase copies already held by other people.
Once the remote is clean, expire local reflogs and prune stale refs where appropriate:
git reflog expire --expire=now --all
git fetch --prune --tagsThese commands can remove recovery paths from the local clone. Keep the separate backup until you’ve verified the result and confirmed there’s no work to restore.
Step 5: Verify the Cleanup and Prevent Another Secret Leak
Goal: Check the rewritten repository and its surrounding systems, then make it harder for a secret to enter Git again.
Scan the cleaned history for the exposed value and inspect the affected file paths. Check each branch and tag that matters, not only the default branch. Review open pull requests and known forks. If a scan still finds the secret, don’t push another partial fix. Find the remaining ref or path and repeat the cleanup from a fresh clone.
Also check the places Git doesn’t control. Old clones, build caches, CI logs, artifacts, and backups may still hold the original commit or value. Clear what you can under your retention and incident rules. Ask collaborators to replace old clones instead of merging from them.
Prevention needs more than a single .gitignore rule. Ignore local environment files before they enter the index, and keep a committed example file limited to variable names and safe placeholders. If a secret file was already tracked, adding it to .gitignore won’t remove it from past commits.
Add scanning before code reaches the remote. A local pre-commit check can catch common patterns early. Server-side secret scanning and push protection can catch mistakes that bypass a laptop hook. Add tested custom patterns if your team uses internal token formats that general detectors may not recognize.
For a team-wide approach, EnvManager encrypts secrets with AES-256 and manages environment files in a shared vault. We can sync values to local machines and CI/CD pipelines, so developers don’t need to copy live keys into tracked files. Use runtime injection for the values a job needs, rather than writing them into source or pipeline YAML.
The API key security guide covers related controls such as least privilege and commit-time checks. Pair those controls with a clear owner for each production credential, so someone knows how to rotate it when an alert arrives.
Test the full workflow with a fake value. Confirm the scanner blocks it before a push, then confirm the app can still get its real secret from the approved store. That gives the team a checkable path from code change to runtime access.
FAQ: Removing Secrets from Git History
Does deleting a secret file remove it from Git history?
No. Deleting the file in a new commit removes it from the current version, but older commits can still contain it. Revoke or rotate the credential first. If history cleanup is needed, rewrite the affected history and check branches, tags, forks, and old clones for remaining copies.
Should I rotate a secret before rewriting Git history?
Yes. Revoke or rotate the credential before starting the rewrite. History cleanup can’t stop someone from using a live key they already copied. Update the app or pipeline with a replacement, confirm it works, then disable the exposed value and review its access records.
Is git-filter-repo better than BFG Repo-Cleaner?
Use git-filter-repo for a controlled history rewrite, especially when you need its sensitive-data removal options. BFG Repo-Cleaner is another option for replacing text or deleting files.
Will a force-push remove the secret from forks and clones?
No. A force-push updates the remote refs you control, but it doesn’t erase other people’s clones or forks. Ask collaborators to reclone or clean their local history. Coordinate with fork owners, and review cached views or pull request references separately.
Conclusion
Revoke the exposed credential first, then rewrite history only if the remaining repository exposure warrants the disruption. Start with a fresh clone, review every affected ref, and coordinate the force-push with your team. For the next commit, move secrets into a managed store such as EnvManager and inject them when the app runs.