Skip to content

543,000 valid credentials sat in public GitHub repos. Check yours

By SunnyKumar Jonwal 10 min read

Truffle Security scanned 224 million public GitHub repositories and more than 58 billion files, and found 543,699 unique credentials that still worked in July. That's the headline figure in coverage by BleepingComputer and SecurityWeek, and the number I'd keep in your head: over half a million live keys, tokens, and passwords, sitting in places anyone can read.

The details are worth a closer look, because they say something about how leaks happen and how long they last. This post covers the study's findings, the reasons "I deleted the commit" doesn't work, and a routine you can run on your own repositories this afternoon. I'm summarizing published reporting, and I haven't verified the data myself, so check Truffle Security's write-up if you want the methodology.

What the study found

Several numbers stand out, as reported.

The median time a unique credential stayed publicly accessible was 784 days. That's more than two years. Roughly 10 percent of the working credentials were older than 6.3 years, and the oldest dated from 2009. These weren't last week's mistakes. They were old leaks that nobody noticed or nobody fixed, and they were still valid.

The 543,699 credentials appeared repeatedly, across more than 1.1 million files and repositories once forks and copies were counted. One leaked key, in other words, can exist in a lot of places.

The researchers also reported that secret density, meaning working credentials per million files, rose from 3.72 in 2015 to a peak of 11.62 in 2025. And they compared the scale with an earlier scan of Hugging Face, where they'd found 221,303 working credentials. The GitHub figure is more than double that.

One more finding is the part I find most instructive. GitHub turned on Push Protection for all users in February 2024. It blocks pushes that contain recognizable secrets, unless you override the warning. Truffle's data says 199,843 of the credentials, about 36.8 percent, were exposed after that date. Push Protection helps, and the leaks didn't stop. Some patterns of secret aren't recognized, some people click through the warning, and some leaks arrive through channels that the check doesn't cover, such as files committed in formats the scanner doesn't parse.

Why this keeps happening

Nobody sets out to publish a database password. A few boring pathways account for most leaks.

A developer hardcodes a key to get something working locally, intends to move it to an environment variable later, and commits before they do. A .env file isn't in .gitignore in a new project. A notebook or a screenshot contains a token in its output. A config file from a tutorial gets copied with the example's real-looking values. A private repository becomes public, because someone wanted to share a project or open-source it, and the history comes along.

Add generated code. AI assistants sometimes produce realistic-looking credentials, and agents with access to your files can write real ones into places they shouldn't, as I described in slopsquatting, leaked secrets, and other AI coding risks. That doesn't make the assistants the main cause. It does mean there's one more route by which a secret can reach a commit.

Then there's the time factor. A leaked key that nobody abuses right away doesn't announce itself. It just sits there, valid, until someone finds it. Automated scrapers watch the public commit feed and try new credentials within minutes, so the dangerous window is often short, but the exposure can last for years if the key never gets revoked.

Why deleting the commit doesn't help

This is the misconception I'd most like to clear up. When you push a secret and then remove it in a later commit, the secret is still in the repository's history. Anyone can check out the earlier commit and read it. Forks made before the cleanup keep their copy. Mirrors, caches, search indexes, and archive services may have a copy as well.

Rewriting history with a tool such as git filter-repo removes the secret from your repository's history, and that's worth doing, but it doesn't remove copies you don't control. So the rule has a fixed order:

  1. Revoke or rotate the credential first. Treat it as compromised from the moment it was public.
  2. Check the logs for abuse during the exposure window.
  3. Then clean the history, if you want to, to stop further copies.

Rotation is the real fix, and history cleanup is hygiene. If you only do the second one, the key still works for anyone who copied it.

A routine for your own repositories

You can check what you've exposed in under an hour. Use a scanner that looks through the full history, not just the current files, because old commits are where forgotten secrets live.

Two open-source tools do this well. Gitleaks is quick and easy to wire into a pre-commit hook, and TruffleHog can verify whether a found credential is still active by testing it against the provider's API.

# Scan the whole history of the current repository for secrets
gitleaks detect --source . --verbose

# Scan history and report only credentials that still work
trufflehog git file://. --only-verified

Run them on every repository you own, including old side projects and forks. Start with the public ones. If a tool reports a hit, find out what the credential is, which service it belongs to, and whether it's still active. Many hits turn out to be test keys or examples, and some turn out to be live production credentials that deserve an immediate rotation.

For anything marked verified by TruffleHog, assume it's live and act now.

Prevent the next one

Detection finds old mistakes. Prevention stops new ones, and a few layers work well together.

Keep real secrets out of the repository entirely. Use environment variables or a secrets manager, and commit a .env.example file with obviously fake placeholder values. In a Laravel project, the framework's default .gitignore already excludes .env, so check that your project still has that line, and check that no one has committed a copy under another name, such as .env.backup or env.txt.

Add secret scanning before commits reach the remote. A pre-commit hook with Gitleaks catches most accidents on your own machine, and a CI job catches the ones that skip hooks. A minimal setup looks like this:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0   # pin to a current release
    hooks:
      - id: gitleaks

Turn on GitHub's own protections. Secret scanning and Push Protection are available for public repositories at no cost, and the settings live under the repository's security options. They won't catch everything, as the study shows, but they cost nothing and they stop the common patterns.

Use short-lived and narrowly scoped credentials wherever you can. A token that expires in an hour and can only read one bucket is a much smaller problem when it leaks than a permanent administrator key. Many cloud providers support temporary credentials and workload identity, which remove the need to store long-lived keys at all.

Give each environment and each service its own credential. When a key is shared across five systems, rotating it means coordinating five changes, and people put it off. When each system has its own, a rotation is quick.

Finally, rehearse the rotation. Know where each credential is used and how to replace it without an outage. The 784-day median exposure suggests that, for many leaks, nobody knew who owned the key or how to change it.

What happens in the first hour after a key goes public

It helps to know what you're racing. Scrapers watch GitHub's public event stream, which lists new commits as they land, and run pattern matching on the contents. A string shaped like a cloud access key or a payment API token gets tried against the provider almost at once. Cloud providers have noticed, and some of them scan public repositories themselves and warn or lock keys that they find. That helps when it works, and it only covers the formats and providers they've built detectors for.

What attackers do with a working key depends on what it can reach. Cloud keys with broad permissions get used to start expensive compute for cryptocurrency mining, to copy storage buckets, or to create new users that outlive the original key. Email and messaging API keys get used for spam and phishing sent from your reputation. Database passwords get used to read and sometimes encrypt or delete data. Source-control tokens get used to read private repositories, which often contain more secrets.

The first sign is often billing, an unusual spike in usage, or a complaint from the provider. By the time you notice, the key may have been used for days. That's why the order matters: rotate first, then investigate.

Rotating the common kinds of credential

The details differ by credential type, and a short tour helps you plan before an incident.

Cloud provider access keys: create a new key, update the systems that use it, confirm they work, then delete the old key. Check for any resources, users, or roles created while the old key was exposed, because attackers like to leave themselves a way back in. Cloud audit logs show who created what.

Database passwords: change the password at the database, then update every application that connects. Check the database's connection logs for logins from unfamiliar addresses, and consider whether the database was reachable from the internet at all. A leaked password for a database that only accepts connections from a private network is a smaller problem than one for an open port.

Third-party API tokens: revoke in the provider's dashboard and issue a new one. Look at the usage page for calls you didn't make. For payment and messaging services, review recent activity right away, since abuse costs real money.

SSH private keys and deploy keys: remove the public key from every server and service that trusts it, generate a new pair, and distribute the new public key. Check server logs for logins with the old key.

Personal access tokens for code hosting: revoke them, then review the audit log for repository clones, new tokens, or changed settings. If the token had access to private repositories, scan those for secrets too, since an attacker who read them now has any keys inside.

Signing keys and encryption keys are the hard ones. Rotating them can break things that were signed or encrypted with the old key, so plan the migration. Know in advance which of your keys fall in this category.

If you find a leaked secret

Here's what the response looks like for a single credential, roughly in order.

Revoke it or rotate it now, at the provider. Don't wait to understand everything first. Then look at the provider's access logs for the period since the leak. Look for use from addresses, regions, or services you don't recognize, and for any actions you didn't take, such as new users, new keys, or large downloads. If you see signs of use, treat it as an incident and widen the review to anything that credential could reach.

Next, remove the secret from the code and replace it with a reference to your secrets store. After that, rewrite history if you want to, and ask collaborators to re-clone. Document what happened, even briefly, because the same mistake tends to repeat on the same team.

A note on Laravel's APP_KEY, since it comes up. If that key leaks, rotating it invalidates encrypted cookies and sessions, and it can break any data you encrypted with Laravel's encrypter, unless you handle the transition. Plan for that before you change it, and use the framework's documented approach for previous keys.

What this says about the bigger picture

I read this study as a reminder that many organizations still don't know where their secrets are. A leak isn't a single bad moment. It's a state, and it persists until someone looks for it. The practical lesson is to look: scan your history, rotate what you find, add checks that run without anyone remembering to run them, and shrink the damage of future leaks with short-lived, narrow credentials.

If you do only one thing today, run a history scan on your most important public repository. The result will either reassure you or save you from becoming a statistic in next year's study.