How to Find and Remove Secrets Hidden in Your Git History
🛡️ Security Intermediate 4 min read

How to Find and Remove Secrets Hidden in Your Git History

A deleted credential still lives in your commit history. Enumerate it, revoke it at the source first, then rewrite history and add push protection to keep it out.

Published: October 10, 2026 • Updated: October 10, 2026
gitsecrets managementincident responsecredential theft

The most alarming part of the October 2026 GhostAction campaign was not that a malicious workflow read the secrets in a repository's current files. It was that the workflow checked out the complete commit history and scanned every past diff for credentials. If your incident response stops at "we rotated the key," you have not finished, because the version an attacker read may still be live and the copy in your history is still there. Here is how to find those secrets and get rid of them properly.

Why Deleted Secrets Are Not Gone

Git is designed to never lose anything. When you delete a line containing an API key and commit the change, the key does not disappear; it moves into the repository's history as part of the diff that removed it. Anyone who can clone the repository, or run a workflow against it, can read that old commit. Forks and mirrors inherit the full history too, so a secret you "removed" can live on in dozens of copies you do not control.

This is the exact mechanism the GhostAction workflow abused, as explained in how a GitHub ActionsGitHub Actions🛡️GitHub's built-in automation and CI/CD platform. It runs workflows defined in YAML files under a repository's .github/workflows directory, and those jobs can read the repository's stored secrets and reach the network. workflow can reach every secret in your pipeline. Treat every credential that has ever been committed as potentially compromised, not just the ones visible today.

Step 1: Find What Is There

Start by searching the entire history, not just the working tree. A history-wide diff search surfaces strings that match credential patterns across every branch and tag. Modern tooling makes this practical: dedicated secret scanners can walk the whole commit graph and flag matches by credential type, and GitHub's own secret scanningSecret Scanning🛡️An automated check that searches code and commit history for credential-shaped strings. Push protection extends it by blocking commits that contain a detected secret before they reach the repository., where available, does this continuously and can alert you to known provider token formats.

Build a concrete inventory from the results: which secret, in which commits, on which branches, and whether it is still valid. You cannot remediate what you have not enumerated. Expect the list to be longer than you think, because years of commits accumulate test keys, debug tokens, and credentials pasted in "just to get it working."

Step 2: Rotate and Revoke First

Before you touch the repository's history, invalidate the secrets themselves. Rewriting history is slow and disruptive; cutting off the credential is fast and is what actually stops the bleeding. For each exposed secret, revoke it at the issuing system and issue a fresh one. Revoke, do not merely rotate: a token that is regenerated but whose old value is still accepted somewhere is not safe.

Order matters. A key that no longer works is harmless even if it stays in your history forever, so prioritise revocation over the cleanup in the next step. This is also the moment to apply the same discipline the GhostAction response demanded: assume the attacker already has the value and act as if it is in use against you right now.

Step 3: Rewrite History to Remove the Secret

Once the secret is dead, you can scrub it from the repository so future clones and scans do not keep flagging it. This means rewriting history, which changes commit identifiers for every commit after the one you edit. Purpose-built tools exist for this: a history-rewriting utility can replace or strip matching content across all commits far faster and more safely than manual rebasing.

Rewriting history is a coordinated operation. It invalidates everyone's local clones, breaks open pull requests, and requires a force-push, so plan it with your team rather than springing it on a Friday. Afterward, every collaborator must re-clone or carefully reset, and you must account for forks, which keep their own copy of the old history regardless of what you do upstream.

Step 4: Prevent It From Happening Again

Cleanup is wasted effort if new secrets keep landing in commits. Close the loop with prevention. Enable push protection so that commits containing recognisable secret formats are blocked before they ever reach the server. Add a pre-commit secret scan to developer machines so the check happens before code leaves a laptop. Keep real credentials out of the repository entirely by loading them from a secrets manager or injecting them at deploy time.

The strategic version of this is to stop depending on long-lived secrets at all, which is the argument in why short-lived credentials beat rotating secrets after a CI breach. A credential that expires in an hour is far less valuable sitting in an old commit than one that works for years.

The Takeaway

After a workflow compromise, the question is not what secrets are in your code but what secrets have ever been in your code. Enumerate the full history, revoke every exposed credential at the source before you clean anything, rewrite history to remove the strings, and put push protection and a secrets manager in place so the problem does not recur. GhostAction only mattered because so many repositories had years of forgotten credentials waiting in their past. The repositories that had cleaned up had far less to lose.