Skip to main content
If a developer accidentally commits a secret to your GitHub repository, treat that secret as compromised and immediately rotate or revoke it. After revocation, you can rewrite repository history to remove the leaked value using git-filter-repo (or replace the exact string across all commits).
A presentation slide titled "Scrub Compromised Secrets - git-filter-repo" with a large blue teardrop-shaped graphic on the right containing the word "Demo." The slide includes a small "© Copyright KodeKloud" notice.
This guide demonstrates a safe workflow using a small test repository and a mock secret. It covers cloning best practices, creating a replace-file, running git-filter-repo, and force-pushing rewritten history.

Lesson overview

  • Create a small test repo on GitHub.
  • Simulate a leaked secret by committing a test file.
  • Revoke the secret in any external service.
  • Use git-filter-repo --replace-text to remove or replace the value across history.
  • Force-push the new history and coordinate with collaborators.

Create a test repository on GitHub

For this demo we used a repo named test-secret (public) with a single README.md.
A dark-mode screenshot of GitHub's "Create a new repository" page with the owner dropdown open and "test-secret" entered as the repository name. Repository configuration options like visibility, README, .gitignore, and license are visible below.
After creating the repo you will see only the initial commit on the main branch and the README.md.
A dark-mode GitHub repository page for a public repo named "test-secret," showing the main branch, an initial commit and a README.md file. The README title "test-secret" is visible and the right sidebar lists repo details (no stars, forks, or releases).

Simulate a leaked secret (local)

  1. Clone the repo locally.
  2. Create a secret.txt file containing a mock secret:
  1. Commit and push the file:
Example push output (condensed):
At this point the secret exists in the repository history (older commits). Before attempting to rewrite history, revoke or rotate the compromised credential with the issuing service (API provider, cloud console, etc.).
Always rotate or revoke any exposed credentials immediately. Rewriting Git history removes the secret from the repo, but external systems, CI logs, or third parties may have already copied or used the secret.

Removing the file from the tip (does not remove it from history)

If you simply delete the file and push, the secret is removed only from the latest tree — earlier commits still contain the secret:
To remove the sensitive value entirely from the repository history you must rewrite the repository history (see next section).

Use git-filter-repo to replace the secret across history

Prerequisite: install git-filter-repo (not included with core Git). Official repo: https://github.com/newren/git-filter-repo Install via pip on many systems:
Important workflow notes:
  • Operate on a fresh clone in a separate directory to avoid corrupting active clones.
  • Rewriting history requires a force-push and coordination with all collaborators because commit IDs will change.
  • For rewriting all refs and tags, use a mirror clone.
Steps used in this demonstration:
  1. Clone the repo into a fresh folder (example cleanup-repo):
If you prefer to rewrite every ref and tag (including refs not fetched in a normal clone), use a mirror clone:
  1. Create a replace-file listing the target text and replacement. Format is OLD==>NEW. For this demo we replace 1234567 with ***REMOVED***. Save it as /tmp/mock-expression.txt:
Notes:
  • Lines beginning with # are comments.
  • Matches are literal by default; include exact surrounding characters if needed.
  1. Run git-filter-repo with the replace-text option:
Typical output (condensed):
  1. Re-add remote (if needed) and force-push the rewritten history back to GitHub:
If you used a mirror clone, push the entire mirror back with force:
Example force-push output:

Verify the result on GitHub

  • Refresh the repository and inspect older commits that previously contained the secret. The sensitive value should now be replaced by ***REMOVED*** (or the replacement text you specified) across rewritten commits.
  • Keep in mind scrubbing Git history does not affect copies of the repository cloned elsewhere, CI logs, or third-party backups. Search for other exposures and rotate credentials where necessary.

Quick reference — common commands

Rewriting history changes commit IDs. After a force-push, all collaborators must reclone or reset their local clones to avoid conflicts. Communicate the change clearly and update any CI/CD pipelines or automation that rely on the repository.

Summary

  • Immediately revoke or rotate any compromised secrets before attempting history edits.
  • Use git-filter-repo --replace-text to remove or replace sensitive strings across all commits.
  • Work on a fresh clone (or mirror clone for all refs), force-push the rewritten history, and coordinate with collaborators because history rewriting is disruptive and irreversible for existing clones.

Watch Video

Practice Lab