How to triage secret scanner findings (and find the four that actually matter)
You ran gitleaks, or GitGuardian, or GitHub’s secret scanning. You got 289 findings. Now what?
The scanner did its job. Detection is the solved half of this problem. The unsolved half is the 289—turning a wall of rows into the short list you actually rotate. That’s triage, and it’s a method, not a feature. Here’s how I do it, step by step. (I run this as a service at Unrotated Security; the method is the same whether you do it yourself or I do it for you.)
1. Dedup across history and branches
Most of your raw count is the same secret seen many times. A key committed once, then cherry-picked across 46 branches and 6,890 commits, shows up dozens of times in the scanner’s eyes. Group by the credential, not by the line. One real secret, not forty findings.
After dedup on a recent run of my own, 289 raw rows collapsed to a handful of unique credentials. The number that matters is unique credentials, not findings.
2. Separate HEAD from history
This is the split that changes everything. A secret in your current HEAD is a live exposure. A secret that was added and later removed—rotated, scrubbed, superseded—is a ghost. It’s still in your git history, and the scanner still flags it, but it’s not actionable.
On that same run, 79 of the findings were historical. Already rotated. Genuinely not actionable, but they’re why the raw count looks scary: every secret you ever rotated and scrubbed still haunts your history in the scanner’s eyes.
Filter to HEAD first. That’s where the real exposures live.
3. Verify liveness (the step most people skip)
“Found” is not “live.” A hardcoded AWS key that was revoked last quarter is still “found” in your repo. The scanner can’t tell the difference. You can.
For each unique credential in HEAD, make a read-only call against its provider to confirm it’s still active—an identity call, not an action. For AWS, that’s STS GetCallerIdentity with the key. For others, the provider’s equivalent read-only endpoint. Where the provider exposes rotation or revocation metadata, check that too. The goal is one bit of information: is this credential still usable, yes or no.
Two rules that matter here. First, don’t store the secret. Use it for the verification call, confirm the result, and drop it. Never write found secret values to a database, a report, or a log. Second, “unverified” is not “safe.” Some providers can’t be verified this way—a Meilisearch key, a Phoenix SECRET_KEY_BASE, anything without a standard identity endpoint. The verification engine comes back false, and it’s tempting to read that as “not live.” It isn’t. It means I couldn’t confirm it’s dead. The right response is treat as live until a human confirms otherwise, not “ignore.”
That distinction is the whole game. A less careful triage reports “0 live secrets found” and an engineering director exhales and closes the dashboard. Four unverified-but-real credentials are still sitting in HEAD against real infrastructure.
4. Filter placeholders and test strings
What’s left after history and liveness is the noise that was never a secret at all: changeme, password123, example JWTs in docs, .env.example values, a metric label like signups_since_… that matched as a key. The scanner was right to flag them—they look like secrets. They aren’t.
On my run, 11 findings were placeholders, test strings, or a single scanner false positive. Log them, so the next scan doesn’t surprise you, and ignore them.
5. Prioritize: Rotate Today, Rotate This Week, Log & Ignore
Now you have a short list. Rank it by impact and certainty:
- Rotate Today—verified live, high impact. An active cloud key with broad permissions. This is an incident.
- Rotate This Week—real credentials in HEAD, lower immediate risk, or unverified against unsupported providers (treat as live). Rotate on a session where someone’s watching, not at 2 a.m.
- Log & Ignore—placeholders, test strings, historical ghosts, confirmed-revoked. Not actionable. Keep the log so the noise stays labeled.
That’s the report. 289 rows → four actions. The scan was raw material; this list is the value.
If you’d rather not do this yourself
The method above is the whole job, and you can run it yourself. If you’d rather hand the wall to someone who does this every week—read-only, no tooling to install, a prioritized report in a week—that’s what Unrotated Security does. You keep the report; you do the rotating. (Here’s the argument for why triage is the part nobody sells, and a real 289-finding teardown if you want to see the method end to end.)