Yaamlabs
Identity

543,699 live keys sit in public GitHub repos

Truffle Security tested secrets in 224 million public repos and 543,699 still worked, with a median age of 784 days. What leaks, why it survives, what to do.

By Yaali. October 3, 2026, 5 min read, Identity, Threat intel, Resilience.

Cover illustration of an overflowing filing drawer with glowing keys tucked between old cards, with the Yaamlabs logo and the text: Leaked GitHub secrets still work years later, 784 days, median age of a live key

Truffle Security scanned 224,553,295 public GitHub repositories and found 543,699 unique credentials that still authenticated when it tested them on July 27 and 28, 2026. The median live credential had been sitting on a public default branch for 784 days, a little over two years. One in ten had been public for more than 6.3 years, and the oldest, a database credential committed in June 2009, still worked.

If your developers have ever pushed to a public repository, some of these could be yours. GitHub's push protection has reduced new leaks of the formats it covers, but it does nothing about keys already public, and many providers do not revoke keys that leak. Whether a leaked key keeps working comes down to who issued it.

Four tiles: 543,699 live credentials from 224 million repositories, a median age of 784 days, 51.8 percent of live credentials of types default push protection does not block, and 199,843 pushed after push protection became the default

How the study worked

The data came from The Stack v3, a snapshot of public code assembled to train AI models. Its crawl closed on August 7, 2025 and covers 58,467,468,698 files. It keeps only each repository's default branch as it stood at crawl time, so deleted branches and rewritten history are not in it. The real number of exposed secrets on GitHub is likely higher.

Truffle matched credential patterns, then tested each candidate against the service that issued it. A key counted as live only if that service accepted it in July 2026, close to a year after the crawl. Because the same secret often appears in many forks, Truffle counted each credential once by its value: 543,699 unique credentials came from 1,103,438 separate exposures.

The largest group was Google Cloud service account keys, with 69,041 live. Behind them came 51,067 MongoDB connection strings and 33,343 Google API keys.

Why push protection did not save them

Push protection is the GitHub feature that rejects a git push when it spots a known secret format in the commit. GitHub turned it on by default for public repositories on February 29, 2024. Truffle compared leak rates in the 12 months before and after that date: for credential types the default blocks, leaks per million files fell by 53 percent, against 7 percent for types it does not block.

Push protection matches provider patterns, meaning token formats a vendor has registered with GitHub. Connection strings and private keys fall under what GitHub calls non-provider patterns, which Truffle describes as opt-in. According to Truffle, 51.8 percent of all live credentials in the study were of a type a default-configured public repository accepts without complaint, mostly database connection strings, Google API keys and private keys.

Push protection also only acts at push time. It does nothing for a key committed in 2019. Of the live credentials, 199,843 (36.8 percent) were pushed after the default changed; the other 63.2 percent were pushed before it.

What decides whether a leaked key dies

Some providers revoke their own tokens when they find them in public code. Others leave it to the owner, and the key keeps working until the owner notices.

Bar chart of the share of committed credentials still live in July 2026: npm tokens about 0 percent, Hugging Face tokens 0.05 percent, GitHub tokens 0.36 percent, Google Cloud service accounts 54 percent, PostgreSQL connection strings 88 percent

Only 1 of 101,886 committed npm tokens still worked. GitHub tokens were close behind at 260 live out of 73,048, and Hugging Face tokens at 15 of 30,437. At the other end, 11,465 of 12,985 PostgreSQL connection strings, about 88 percent, still connected. Google Cloud service accounts sat in the middle at 69,041 of 126,963, roughly 54 percent.

A database URL has no issuer watching for it. The password inside it stays valid until someone with access to the database changes it, and that only happens if the team knows the string is public.

What to do

Start by treating any credential that has ever been committed to a public repository as compromised, whether the repository is still public, was later made private or had the file deleted. Forks and clones keep the old commit.

  1. Find what you have exposed. Scan your organization's repositories, including history, with a tool that verifies findings against the issuer. With TruffleHog that is trufflehog github --org=<your-org> --only-verified. Include personal repositories of developers who have worked on your code, since many leaks come from side projects and test copies.
  2. Revoke and reissue, do not just delete the file. For an AWS key, deactivate it in IAM and create a new one. For a Google Cloud service account, disable and delete the key under IAM and Admin, Service Accounts, Keys. For a PostgreSQL connection string, change the role's password with ALTER ROLE <name> WITH PASSWORD '<new>'; and update the applications that use it. For a MongoDB Atlas string, rotate the database user's password.
  3. Check whether the key was used. For AWS, search CloudTrail for the AccessKeyId over the full period it was public. For Google Cloud, filter Cloud Audit Logs on the service account's email as the principal. For databases, look at connection logs for source IP addresses outside your hosting ranges.
  4. Turn on the detection you are missing. On GitHub Team or Enterprise Cloud with Secret Protection, open the repository's Settings, Advanced Security, and enable Non-provider patterns under Secret Protection. That adds MongoDB, MySQL and Postgres connection strings, RSA, OpenSSH and PGP private keys, and HTTP authentication headers to scanning. Findings appear under a separate Other tab in the secret scanning alerts.
  5. Make the next leak expire on its own. Replace long-lived keys with short-lived ones: workload identity federation for Google Cloud, IAM roles and OIDC federation for AWS from CI pipelines, and database credentials issued per session where your platform supports it.

AI training data carries these secrets too

The Stack v3 exists to train AI models, so every live key in this study also sits in a training corpus. Truffle found the same problem when it scanned 7.6 petabytes of Hugging Face training data and found 221,303 working credentials. This GitHub result is more than double that. Deleting a commit today does not remove a key from a dataset that copied it in 2025. Only revoking the key does.

We look for committed and leaked secrets as part of attack surface management, and review how keys are stored and rotated in cloud and Kubernetes security work. If you want to know what your organization has left in public code, open the chat and Yaali, our AI agent, will pass your question to an engineer.


Sources: Truffle Security, Truffle Security, Hugging Face training data scan, GIGAZINE, Cyber Security News, GBHackers, GitHub Docs, non-provider patterns.

Read next

Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.