Truffle: 543,699 live credentials sitting in public GitHub repos
Truffle Security scanned 224M public GitHub repos and found 543,699 credentials that still authenticated in July 2026 — including 69,041 valid Google Cloud service-account keys.
Truffle Security scanned 224 million public GitHub repositories and found 543,699 unique credentials that still authenticated when tested in July 2026 — not expired, not rotated, live. The write-up, GitHub Repos Exposed 543,699 Credentials. Nobody Revoked Them., is the primary; the numbers below are theirs.
What it demonstrates
The finding isn't that secrets get committed — everyone knows that. It's that they stay valid for years after exposure:
- Median exposure: 784 days. The oldest still-working credential was committed in 2009; roughly 10% were older than 6.3 years.
- Google Cloud service accounts: 126,963 found, 69,041 still valid — more than half never revoked. These are often high-privilege, non-interactive keys, exactly the kind that make a quiet pivot into cloud infrastructure.
- npm tokens: 101,886 found, only 1 still worked. The contrast is the story — npm's automated revocation pipeline works; most other issuers' don't.
The gap between npm and Google Cloud is the actionable insight: the problem isn't detection, it's revocation discipline. A leaked secret nobody rotates is a leaked secret an attacker can use at leisure.
Limits and caveats
- The corpus was a crawl assembled for LLM-training that closed August 7, 2025; validity testing ran in July 2026. So "live in July 2026" is the hard claim; the exposure set is up to ~11 months stale at the edges.
- "Still authenticated" means the key returned a valid response to a benign check, not that it was abused. Truffle tested validity, not exploitation.
- Counts are for public repos only. Private-repo and CI-log exposure is a separate, larger problem this study doesn't touch.
On GitHub's own control: push protection has been on by default since February 2024, yet Truffle reports just under 200,000 credentials pushed after that date, and says 51.8% of what's still live is in a format push protection doesn't recognize. The block halves the rate for recognized credential shapes — it is not a backstop for custom or uncommon ones.
What to do
- Rotate, don't just delete. Removing a secret from the latest commit leaves it in history and, more importantly, still valid at the provider. Revoke it at the source first.
- Scan full history, not
HEAD. Use TruffleHog or your scanner of choice against the whole git history and any forks. - Set expiration on everything that supports it. Short-lived, auto-expiring credentials turn a leak into a time-boxed problem.
- Audit Google Cloud service-account keys specifically. Prefer workload identity federation over long-lived JSON keys; disable and delete keys you can't tie to a live workload.
- Don't rely on push protection alone for non-standard secret formats — add server-side secret scanning that you control.
Context
Half a million live keys in public code is a floor, not a ceiling — public repos are simply the part that's easy to measure, with private repos, CI logs, and container layers unquantified alongside it. The durable lesson for AppSec teams is unglamorous: committing a secret is a mistake, but not revoking it is the one that gets you owned, sometimes a decade later.