Safeguard
Supply Chain Security

How to Scan Large GitHub Orgs for Exposed Secrets Responsibly

28.65 million new secrets landed on public GitHub in 2025 alone. Here's a research methodology for finding them at scale without becoming the next incident.

Safeguard Research Team
Research
7 min read

GitGuardian's State of Secrets Sprawl 2026 report, covering calendar year 2025, counted roughly 28.65 million new hardcoded secrets committed to public GitHub — a 34% year-over-year jump and the largest single-year increase the firm has recorded since it began publishing the study. That follows the 2025 edition's tally of 23,770,171 secrets found in 2024, itself a 25% increase over 2023. GitGuardian has also reported that public repositories using GitHub Copilot show a secret-leakage rate of 6.4%, and that roughly 70% of secrets leaked in 2022 were still valid and unrevoked years later — meaning most organizations that get scanned once and never again are sitting on live credentials right now. Scanning a large set of public repositories for exposed secrets is one of the highest-signal, lowest-cost research exercises in application security, but doing it across an org, a GitHub Enterprise instance, or a swath of public repositories raises real methodology and ethics questions: how do you enumerate at scale without tripping rate limits, how do you tell a real key from a UUID that merely looks like one, and — critically — what do you do the moment you find something live. This piece walks through the methodology end to end.

Why does history scanning matter more than scanning the current branch?

History scanning matters because deleting a secret from the latest commit does not remove it from the repository — it only removes it from HEAD . Git stores every prior version of every file as an immutable blob, so a key committed and then "removed" in a follow-up commit is still fully present and clonable via git log -p or by walking the object database directly. A scan limited to the current checkout will miss this entirely, which is why credential leaks routinely surface in repositories that look clean on the surface. A shallow clone ( git clone --depth 1 ) is fast and fine for a first pass across thousands of repos, but any repository that scores a hit — or any repository flagged as high-value (infrastructure, deployment, internal tooling) — needs a full-history clone and a walk of every commit, not just the tip. This is also why GitHub's own secret scanning and push-protection features run at commit time, before a secret ever lands in history: prevention is cheaper than any downstream detection pass, full history or not.

How do you enumerate a large set of repositories without getting rate-limited or missing forks?

Enumeration at scale runs against GitHub's REST or GraphQL API rather than scraping the web UI, and the practical constraint is GitHub's rate limits — 5,000 requests per hour for an authenticated token, higher for GitHub Apps with installation tokens. A methodology built for hundreds or thousands of repositories needs to paginate the org's /repos endpoint, respect the X-RateLimit-Reset header, and back off rather than retry blindly. Forks matter more than most researchers assume: a secret purged from the parent repository's history can still live untouched in a fork nobody thought to check, and GitHub's fork graph API lets you enumerate them. Archived and low-star repositories are frequently the highest-yield targets precisely because they're unmaintained — nobody rotated the credentials because nobody was watching. A defensible methodology scopes the target set explicitly (a named org, a public search query, a list of forks) before scanning, rather than crawling GitHub opportunistically, both for rate-limit hygiene and because unscoped crawling raises its own ethical questions about consent and intent.

What's the tradeoff between regex/issuer patterns and entropy detection?

Issuer-specific regex patterns — matching the literal structure of an AWS access key ( AKIA followed by 16 characters), a GitHub personal access token, or a Stripe live key — produce very few false positives because the format is a fixed, documented contract from the issuer. The tradeoff is coverage: a regex only catches secrets whose format someone has already catalogued, and internal or custom-issued tokens (a company's own JWT-based service credential, for instance) have no public pattern to match against. Shannon-entropy detection fills that gap by flagging any string whose character distribution looks more random than natural-language or structured text, which catches secrets no one has pattern-matched yet — but it also flags commit hashes, UUIDs, and base64-encoded images at a much higher false-positive rate. A production-grade pipeline runs both as overlapping layers rather than choosing one: issuer patterns for the 90%+ of secrets that are cloud-provider or SaaS-vendor tokens with known shapes, and entropy plus structural classification as a backstop for everything else, with the entropy layer's output treated as lower-confidence until corroborated.

Why isn't a pattern match the end of the finding?

A regex match only tells you a string has the shape of a secret — it says nothing about whether that credential is still valid. The single most consequential step in this methodology is verification: making a narrowly-scoped, low-privilege API call to the issuing service to confirm the credential is live before treating the finding as urgent. For an AWS key, that's a call to sts:GetCallerIdentity , which authenticates without touching any customer data. For a GitHub PAT, it's a call to /user ; for a Stripe key, a read-only call to /v1/balance . This distinction is exactly what separates a finding that is "detected" from one that is "confirmed live and exploitable," and it's the design principle behind Safeguard's own secrets-scanning pipeline, which runs issuer-specific pattern matching, generic pattern matching, and entropy-based detection as three layers and then verifies issuer-pattern findings against the issuing API before marking them verified versus unverified — because a security team triaging thousands of regex hits and one triaging a handful of confirmed-live credentials are doing two very different jobs.

Why does exposure in a private repo still matter?

Uber's 2016 breach is the clearest illustration that "private" is not "safe." Attackers who obtained stolen employee credentials used them to access a private GitHub repository containing hardcoded AWS access keys, then used those keys to pull records on an estimated 57 million riders and drivers from an S3 bucket. Uber's then-Chief Security Officer arranged a $100,000 payment to the attackers disguised as a bug-bounty payout and concealed the breach for over a year; Uber later paid a $148 million multistate settlement, and the CSO, Joe Sullivan, was convicted of obstruction of justice in October 2022. The lesson for a scanning methodology is that repository visibility is not a security control — a private repo behind a single compromised employee credential is exactly as exposed as a public one, which is why credential-in-code detection needs to run on private and internal repositories with the same rigor as public ones, not as an afterthought reserved for the public surface.

What does responsible disclosure actually require here?

Responsible disclosure in this context means verifying liveness with the least invasive API call available, never using a discovered credential to access, modify, or exfiltrate data beyond what confirms it's live, and reporting findings to the repository owner or the organization's listed security contact rather than publishing them. GitHub's own coordinated-disclosure guidance and most bug-bounty programs draw a hard line between "confirmed this key authenticates" and "used this key to read production data," and crossing that line turns a research finding into unauthorized access, with real legal exposure regardless of intent. Practically, that means logging the minimum verification call, redacting the secret value itself in any report or ticket, and giving the owner a reasonable window to revoke and rotate before any public write-up — the same revoke-rotate-purge-notify sequence that a mature secrets program runs automatically once a credential is confirmed live.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.