Safeguard
DevSecOps

Push Protection Stops the Next Secret, Not the 500 Already in Your History

Blocking a credential at git push is genuinely good and solves exactly one half of the problem. The other half is every commit you have ever made, and deleting those commits is not the fix either.

Karan Patel
Security Engineer
4 min read

Push protection is a good control. It matches a push against known credential formats and rejects it before the secret reaches the remote, which turns a leaked key into a five-second annoyance instead of an incident.

It also does nothing about the commits already in your repository, and that gap is where most organisations actually live.

Two problems that look like one

Prospective: stop new secrets entering. Push protection, pre-commit hooks, developer education. Mostly solved, and cheaply.

Retrospective: deal with the secrets already committed. Not solved by any of the above, because they operate on new pushes and every historical secret arrived before you turned them on.

The retrospective problem is the larger one for any repository older than the control. A repo with five years of history has been touched by people who joined and left, during incidents when someone hardcoded a key to ship a fix, and in the early days before anyone thought about it.

Push protection's real limits

Signatures only. Detection works by matching known formats — provider prefixes, structured tokens, high-confidence patterns. That covers AWS keys, cloud provider tokens, and a long list of SaaS credentials, because those vendors publish their formats.

It does not reliably catch your own internal API keys, a database password in a connection string, a private key pasted into a YAML file, or a shared secret that happens to look like a random string. Anything without a distinctive shape is a coin flip.

New pushes only. Force-pushing an old branch, importing a repository, or restoring from a mirror can bring historical secrets past a control that only inspects the delta.

Bypassable by design. Push protection includes an override, because false positives on test fixtures would otherwise block work entirely. Overrides are logged. Somebody has to read the log.

Why deleting the commit does not work

The instinct on finding a historical secret is to remove it — rewrite history with git filter-repo, force-push, done.

It is not done, for four reasons.

Forks and clones keep it. Every clone taken before the rewrite still has the object. On a public repository that is an unbounded set of copies you cannot reach.

Hosting platforms cache unreachable objects. An orphaned commit can remain retrievable by SHA on some platforms long after the branch is rewritten, and the SHA is in the pull request history.

Mirrors, CI caches and backups. Build caches, artefact stores, backup snapshots and internal mirrors all hold copies on their own retention schedules.

Rewriting is disruptive. Every open branch and pull request needs rebasing, every clone needs re-cloning, and the pain is directly proportional to team size — which biases teams toward not doing it, and toward feeling like they have addressed the problem when they decide not to.

The correct first action is rotation. Invalidate the credential at the provider. Once it is revoked, the copies do not matter — the secret in a thousand clones is a string with no privileges attached. Rewriting history afterwards is tidying, and it is optional.

Doing the historical scan

Scan the full history, not the working tree:

gitleaks detect --source . --log-opts="--all"
trufflehog git file://. --since-commit HEAD~10000

Two things to plan for.

Verification matters more than detection. Tools that check whether a found credential is still live turn a list of two thousand hits into a list of nine that need action today. Without that, the output is unactionable and gets ignored, which is the same as not running it.

Triage by exposure, not by count. A live production key in a public repository is an incident. A revoked test credential in a private repo from 2019 is a cleanup task. Sorting by severity of the credential type alone will put those in the wrong order.

The control that actually reduces the rate

All of the above manages a problem rather than preventing it. The prevention is architectural: applications get credentials from an identity-based mechanism rather than a stored string.

Workload identity federation, instance roles, OIDC-based CI authentication to cloud providers, short-lived tokens from a secrets manager. In each case there is no long-lived credential to commit, because the credential does not exist as a value a human ever holds.

That migration is real work and it is not uniform across every system. But it is the only intervention that changes the shape of the problem instead of adding another filter to it — and the teams that have done it stop appearing in their own secret-scanning reports, which no amount of push protection achieves.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.