How to Remove API Keys and Secrets From Git History
Removing a secret from the latest file does not erase it from earlier Git commits. If an API key, password, or token was committed, revoke or rotate it immediately; then assess where it was shared and, when needed, rewrite the repository history. This guide covers a careful cleanup using git-filter-repo and the follow-up steps that keep the old secret from resurfacing.
First: revoke or rotate the exposed credential
Treat a committed credential as compromised, even if the repository is private or the commit was later removed. Revoke the key or token in the service that issued it, create a replacement if the application still needs one, and update the application's approved secret store. Review the provider's audit logs and notify your security or platform team if your organization requires it.
Do this before rewriting history. History cleanup reduces future exposure, but it cannot make a credential safe again or prove that nobody copied it.
Identify the scope of the secret
Find which files and commits contain the exposed value, and check whether it reached a remote, a fork, a pull request, a release, a build log, or another system. Avoid copying a real credential into chat, tickets, terminal commands, shell history, or a new repository file while investigating. Use your provider's secret-scanning alerts or other approved tools when available.
If the file itself should be removed from every commit, record its repository-relative path. If the file must remain but a particular value should be redacted, plan a replacement rule instead. Make a verified backup before rewriting any shared history.
For a commit that exists only locally and has never been shared, you may be able to amend or reset it without a coordinated remote rewrite. If there is any possibility that others or systems already received the commit, follow your team's shared-history cleanup process.
Remove the secret with git-filter-repo
The recommended general-purpose tool for rewriting Git history is git-filter-repo. Install it using its official documentation, then work in a fresh mirror clone so you have the full set of remote refs and a separate working copy:
git clone --mirror https://github.com/OWNER/REPOSITORY.git repository-cleanup.git
cd repository-cleanup.gitReplace the example URL with your repository's URL. To remove an entire file from all rewritten history, use its exact path relative to the repository root:
git filter-repo --sensitive-data-removal --path config/secrets.env --invert-pathsIf the file should remain but the exposed value needs to be replaced everywhere, create a private replacements file outside the repository. Its format can be:
literal:EXAMPLE_SECRET==>***REMOVED***In that example, replace EXAMPLE_SECRET locally with the exact exposed value without putting the real value in a command or committing the replacements file. Keep the file access-restricted, do not share it, and remove it securely after cleanup. Then run:
git filter-repo --sensitive-data-removal --replace-text ../replacements.txtUse the mode appropriate to your case, and check the tool's current documentation for version-specific flags and behavior. The tool may remove the origin remote as a safety measure; inspect the result locally, then add the correct remote back only when you are ready to publish the rewritten history.
Rewriting changes commit IDs and may also affect tags, branches, and other refs. Verify the cleaned history and the current application files before pushing. If the repository is hosted on GitHub, follow GitHub's sensitive-data removal guidance for repository-specific cleanup, including cached views and pull-request references.
Coordinate the rewritten history with your team
Replacing remote history is disruptive. Schedule the change, notify collaborators, pause pushes, and agree on a recovery plan before publishing. Once the approved rewrite is ready, restore the remote URL if needed, then push the rewritten branches and tags using the procedure agreed with your team and host:
git remote add origin https://github.com/OWNER/REPOSITORY.git
git push --force --all origin
git push --force --tags originThese force pushes replace shared branch and tag history; do not run them without repository-owner approval and a coordinated plan. Protected branches may require a temporary, approved change to their protection settings. Confirm the remote state afterward.
Collaborators should follow the team's recovery instructions, often by making a fresh clone. Avoid merging or pushing old local branches that contain the removed commits, since they can reintroduce the secret. Coordinate cleanup of forks and any connected mirrors, CI logs, artifacts, backups, or caches as applicable.
Prevent secrets from entering Git again
- Load credentials from environment variables or a dedicated secret manager instead of writing them into source files.
- Add local secret files to
.gitignore, and verifygit statusbefore staging changes. - Use secret scanning and pre-commit checks as additional safeguards; they do not replace credential rotation after a leak.
- Grant credentials limited permissions, set expiration where possible, and know how to revoke them quickly.
Git history secret-removal FAQ
Is deleting the secret in a new commit enough?
No. Earlier commits still contain the original value. Revoke the credential, then decide whether a history rewrite is appropriate for the repository and its collaborators.
Does rewriting Git history revoke an API key?
No. Only the service that issued the credential can revoke or rotate it. History cleanup is a separate step to reduce future exposure.
Can the secret still be found after a force push?
Potentially. Other clones, forks, caches, pull-request references, backups, and logs may retain old data. Work with your host and collaborators to address those copies; never assume that rewriting a branch erased every copy.