How To

Secrets Detection Automation: Why Leaked Credentials Keep Winning

Deleting a leaked secret doesn't kill it. Part one maps why credentials keep landing in repos, why the backlog grows faster than anyone clears it, and how validity checks and honeytokens cut it to the few keys that matter.

9 min read
Cover Image for Secrets Detection Automation: Why Leaked Credentials Keep Winning

Secrets Detection Automation: Why Leaked Credentials Keep Winning

In 2023, an engineer committed an API key, noticed the mistake eleven minutes later, and force-pushed it out of history. The key is still valid today. Nobody rotated it, because as far as the commit log is concerned, it never existed.

That gap, between "I removed it from the branch" and "the credential is actually dead," is where most secrets incidents live. Secrets detection automation exists to close it, but only if you understand what it's catching and why the obvious fixes don't work. This guide covers why secrets keep landing in repositories, why deleting them isn't remediation, why the incident backlog grows faster than anyone can clear it, and how validity checking and honeytokens turn a noisy alert stream into a short list you can act on.

This is part one of a three-part series. Part two is a hands-on secrets response walkthrough using GitGuardian's native tooling; part three covers continuous secrets response with an AI agent.

01Source

Leaks keep landing

Env files, debug commits, notebooks and CI logs leak at the rate your team commits.

02Fix

Deletion is not remediation

A pushed secret lives in every clone, fork and runner cache. Only revocation kills it.

03Queue

The ownerless backlog

Turning on scanning surfaces hundreds of incidents at once, with nobody budgeted to work them.

04Triage

Validity first

A key that still authenticates is an incident. A key that does not is cleanup.

05Tripwire

Honeytokens

Decoy credentials that only fire when someone is already using stolen access.

06Cadence

Snapshots go stale

A one-time scan is accurate for a day. New leaks and validity changes arrive continuously.

The six realities this guide covers, each with a command or console path you can run today.

Why the usual playbook breaks

Most teams treat a leaked secret like a bug: find the line, delete it, close the ticket. That model fails for structural reasons, and none of them get better with a faster scanner.

  • Git is append-only in practice. A pushed object is copied to every clone, fork, CI cache and mirror. You control one of those copies.
  • Detection and response live in different systems. GitGuardian sees the repository. The key lives in AWS, GCP, Stripe or your database. Killing it means leaving the tool that found it.
  • Urgency depends on facts the alert doesn't carry. Two incidents with the same detector name can be a dead test key and a live production credential. Telling them apart takes a call to the provider.
  • The work has no owner by default. Scanning creates tickets. It doesn't create engineer-hours.

Every section below is one of those reasons showing up in a real repository.

01Leak

Pushed

The secret reaches a shared remote. From this moment it is burned.

02Signal

Detected

GitGuardian opens an incident within minutes of the push.

03Decision

Triaged

Is it valid, what does it open, who owns it. This is where the queue stalls.

04Response

Revoked and rotated

Kill the key at the provider, issue a replacement, update every consumer.

05Proof

Verified

The old key fails, the new one works, the incident closes with a reason.

Detection takes minutes. The days go to steps three to five, which detection tooling doesn't run for you.

1. Why secrets keep landing in repositories

What it is. A hardcoded credential (an AWS access key, a database URL with an embedded password, a third-party API token) committed into source control where it should never be. GitGuardian's own research puts the scale in the millions of new leaks detected across public GitHub every year, and private repositories are worse, not better, because nobody assumes they need scanning.

Why it happens. Less through carelessness than through the shape of normal work. A .env file gets committed because someone edited .gitignore after the first commit. A debug session hardcodes a token "just to test," and the commit ships before anyone reverts it. Jupyter notebooks keep output cells with live tokens in them. CI logs print an environment variable during a failing build and get archived. Config-as-code sprawl means credentials live in more file types than any reviewer can track.

How to detect it. Scan the full history of a repository, not just the working tree, with GitGuardian's CLI, ggshield:

pip install ggshield
ggshield auth login
ggshield secret scan repo /path/to/your/repo

That command walks every commit reachable in the repository, which is the only scan that tells you the truth about what has been exposed. A working-tree scan only tells you what's exposed today, which is the least interesting question.

Typical impact. On a mid-sized codebase, a first full-history scan routinely turns up dozens to hundreds of distinct secrets, most of them years old and long forgotten.

2. Why deleting the secret isn't remediation

What it is. The instinct after a leak is to delete the offending line, commit, and move on. On a shared repository that does almost nothing for your security posture.

Why it happens. People reason about Git the way they reason about a text file: remove the text and it's gone. Git doesn't work that way. Once a commit is pushed, it exists in the reflog, in every clone, in every fork, in every developer's local cache, and in any CI runner that fetched it. A force-push rewrites your branch pointer. It doesn't reach into the laptops and build agents that already have the object. The secret is burned the moment it's pushed.

How to detect it. Confirm a secret still exists in history even after a "cleanup" commit:

git log --all --oneline -S 'AKIA' -- .

The -S flag searches for commits that added or removed the literal string. AKIA is the prefix of long-term AWS access key IDs, so anything that prints means an AWS key passed through your history. If it appears, deletion didn't help you.

The only real fix. Revoke and rotate at the provider: invalidate the leaked credential and issue a new one. History rewriting (with git filter-repo or the GitHub secret-removal guidance) is hygiene you do after the key is dead, never instead of killing it.

ActionWhat it changesDoes the key still work?
Delete the line and commitThe latest treeYes, and it's still in history
Force-push a rewritten branchYour remote's branch pointerYes, in every existing clone and fork
git filter-repo across the orgHistory on the remotes you controlYes, until it's revoked
Revoke at the providerThe credential itselfNo. This is the fix

3. The incident backlog nobody owns

What it is. Turn on scanning across an organization and you don't get ten alerts, you get a backlog. Hundreds, sometimes thousands, of open detections surface at once, each one an incident on the dashboard with no assignee and no clear disposition.

Why it happens. Detection is cheap and remediation is expensive. Every historical leak becomes a ticket, but nobody budgeted engineer-hours to work them. Without ownership the backlog becomes wallpaper: present, ignored, and growing every time a new repo is onboarded.

How to detect it. Look at the raw count and its trend in GitGuardian dashboard → Incidents → Secrets, filtered to Status: Triggered. Or pull it from the API with a read-scoped token:

curl -s -H "Authorization: Token $GG_TOKEN" \
  "https://api.gitguardian.com/v1/incidents/secrets?status=TRIGGERED&per_page=100" \
  | jq '[.[] | select(.assignee_email == null)] | length'

If the open count only ever climbs and most incidents have no assignee, you have a backlog problem, not a detection problem. The question is never "do we have leaks." You do. It's "which of these can hurt us right now."

Typical impact. An unmanaged backlog means the one live, high-privilege key that matters is buried under hundreds of stale, harmless ones. Triage is the whole game.

4. Validity checking: the triage signal that matters most

What it is. A leaked credential is only dangerous if it still works. Validity checking asks the credential's provider exactly that (is this AWS key active, is this token still accepted) and tags each incident valid, invalid or unknown. It's the single most useful signal for cutting a thousand-item backlog down to the handful that need action this hour.

Why it matters. A stale key rotated out two years ago is noise. A valid AWS key with broad IAM permissions sitting in a public fork is a breach in progress. Same detector, completely different urgency, and only validity separates them.

How to detect it. In GitGuardian dashboard → Incidents → Secrets, apply the filter Validity: valid and sort by severity. Those are the incidents that justify waking someone up. The same cut over the API:

curl -s -H "Authorization: Token $GG_TOKEN" \
  "https://api.gitguardian.com/v1/incidents/secrets?validity=valid&status=TRIGGERED&ordering=-date"

Then work out what a suspected-live AWS key opens. Ask AWS who it belongs to, from a throwaway shell that doesn't touch your own profile:

AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... aws sts get-caller-identity

If that returns an ARN, the key is live and you know exactly which account and principal it opens. From your own admin session, aws iam get-access-key-last-used --access-key-id AKIA... tells you when and where it was last used, which is the first hint of whether someone else found it before you did.

Typical impact. On most first scans the valid set is a small fraction of the total backlog, often well under ten percent. That's why validity-first triage turns an unworkable list into a short one.

5. Honeytokens: tripwires for the breaches you can't see

What it is. A honeytoken is a deliberately planted decoy credential: a fake AWS key that grants no real access but fires an alert the instant anyone uses it. You place it somewhere an intruder would look, such as a config file, a CI variable, an internal wiki or a backup archive. Nothing legitimate ever touches it, so any activity is by definition unauthorized.

Why it matters. Everything so far has been about your own repositories. Honeytokens point outward. They tell you when someone is already inside, poking around code or infrastructure they shouldn't have. A trip isn't a hygiene finding. It's evidence of an active intruder, with the source IP and timestamp of whoever tried the bait.

How to detect it. Create one in GitGuardian dashboard → Honeytokens → Create honeytoken, commit it into a plausible-looking file, and wait. GitGuardian's honeytoken docs cover placement strategy. The discipline is to seed them where a real attacker would rummage, not where your own tooling will trip them by accident.

Typical impact. Most honeytokens never fire, and that's the point. The one that does gives you early warning of a breach no repository scan could have caught, often days or weeks before other signals would.

6. Why point-in-time scanning keeps losing

A one-time scan gives you a snapshot, and secrets don't respect snapshots. New leaks arrive continuously.

Every debug commit, every misconfigured .gitignore, every notebook checked in with a live output cell, every CI pipeline that logs an environment variable is a fresh chance, and they happen at the rate your team commits code. Meanwhile the response side never speeds up: revoke the key at the provider, rotate the replacement, redeploy every service that used it, and verify nothing broke. That loop is human, slow, and unchanged by better detection.

So the backlog reopens the day after you clear it, and the validity of any incident can flip the moment someone reuses an old credential. Scanning harder doesn't fix this. Making triage and response continuous does, which is where the rest of the series picks up.


What to do this week

  1. Run ggshield secret scan repo on your three most important repositories and count what comes back.
  2. Filter the dashboard to Validity: valid and give every result a named owner.
  3. Revoke and rotate the valid ones at the provider before touching history.
  4. Plant two honeytokens: one in a CI config, one in a private repo you'd want to know was cloned.

Part two turns that list into a pipeline: pre-commit hooks, a CI net nobody can skip, API-driven triage and honeytoken placement, all on GitGuardian's own tooling.

Make secrets response continuous

Everything above can run without anyone watching the dashboard. CloudThinker connects to GitGuardian through a read-only API token and continuously triages new incidents and honeytoken trips by validity and blast radius, so the live, high-privilege leak surfaces the moment it appears. The agent proposes each revoke and rotate step, and acts within Auto Mode, so you decide what runs on its own and what waits for approval.

Try CloudThinker free with 100 premium credits and no card required, or continue the series with the native-tooling response guide.

gitguardiansecuritysecretssecretsdetectionggshielddevsecopscredentials