The short version: GitGuardian tested 4,802 leaked GitHub App private keys pulled from a dataset of more than 500,000 exposed RSA keys and found that 474 of them — about 10% — still authenticated against GitHub’s API, covering 440 distinct Apps. Unlike personal access tokens or OAuth tokens, GitHub App private keys carry no expiration date; they stay valid until someone manually deletes them, sometimes for years. Two of the affected Apps belonged to organizations developers will recognize: a key tied to the CDC’s GitHub organization sat exposed for roughly 17 months, and a key for the dev-tools company BuildBuddy could have let an attacker rewrite the source behind its build CLI and servers. Neither incident involved a weak password. Both involved a credential type that was designed to be powerful and long-lived, then handled the same casual way as any other API key.
What GitGuardian actually found
GitGuardian, the secrets-detection vendor, pulled the sample from its own archive of publicly leaked secrets: over 500,000 exposed RSA private keys collected from public code. Narrowing that set to keys found in a GitHub-related context alongside a nearby App ID left 4,802 candidates. For each one, researchers signed a JSON Web Token and called GitHub’s /app endpoint — if the key were still valid, the API would return the App’s metadata.
The permission breakdown gets worse the further you look. 72% of the compromised Apps could read private repository content, 40 could administer self-hosted runners, and 98 could control workflows — any one of which is a plausible path to code execution on a company’s own infrastructure. Installation counts per App ranged from zero up to 303, and 59% of the affected Apps had only a single installation, suggesting most were internal tools built by one team rather than public integrations. In 156 cases, the key had leaked in a repository the App’s own maintainer didn’t even own — forked, mirrored, or copied code carrying someone else’s credential along with it.
Why "rotate your API keys" checklists miss this credential entirely
Most credential-hygiene guidance is built around tokens that fail safely by default. Personal access tokens can be scoped to expire in 30, 60, or 90 days. OAuth access tokens typically live for an hour and refresh behind the scenes. GitHub App private keys work differently: the RSA key itself is used to sign a JWT, which is exchanged for a short-lived installation access token — usually valid for about an hour. That part expires fine. The problem is the signing key that mints those tokens. It has no TTL. It stays valid indefinitely, for as long as the App exists, until a human being goes into GitHub’s settings and deletes it.
That single design detail is why a key that leaked in a commit in 2020, tied to the unmaintained Crusher.dev testing framework, was still functional when GitGuardian tested it — three years after the project stopped being maintained. Nobody had to make a mistake twice. They just had to never notice the first one.
Case one: a federal health agency’s key, exposed for 17 months
The most sensitive example GitGuardian disclosed involves an organization called cdcent and a repository belonging to CDCGov, the official GitHub organization of the U.S. Centers for Disease Control and Prevention.
1 · Leak
A private key for an App owned by cdcent was exposed in April 2025 inside a CDCGov repository. The App was installed only in the cdcent organization, with write access to two private repositories.
2 · Exposure window
One of those two repositories appears, based on public documentation, to mediate between CDCGov’s main repositories and the agency’s Azure infrastructure. GitGuardian said the key could plausibly have enabled arbitrary code execution inside CDC’s Azure tenant, though researchers did not interact with the repository to confirm it.
3 · Disclosure
GitGuardian reported the exposure through the HHS responsible-disclosure portal on September 4, 2026.
4 · Revocation
The credentials were revoked on September 18, 2026 — roughly 17 months after the key first leaked.
To be clear about what is and isn’t known: GitGuardian did not claim the key was exploited, and it deliberately avoided testing its write access against a live production repository. The finding is that the key could authenticate and could write for a year and a half without anyone at the agency noticing or rotating it.
Case two: BuildBuddy’s own development key
The second named case is smaller in blast radius but easier to picture, because it happened to a company whose entire product is developer infrastructure. BuildBuddy builds remote build and caching tools for Bazel, the open-source build system.
1 · Leak
A private key for an App called "BuildBuddy (local)" leaked in June 2025 inside one of the company’s own repositories. The App was meant purely for internal development and was installed on only 10 organizations, including BuildBuddy’s own buildbuddy-io account.
2 · Scope of access
The App held write and administer permissions on buildbuddy-io/buildbuddy, the repository containing the source for both the client CLI and the server components of the platform. An attacker with write access there could, in theory, have tampered with code that ships to every BuildBuddy CLI user and every company running BuildBuddy’s server components on their own infrastructure.
3 · Response
After GitGuardian’s report, BuildBuddy’s security team took the App down quickly. The company investigated and found no evidence of malicious exploitation.
GitGuardian also flagged a third, larger-scale example: a key for an App called "Access Tokens for GitHub Actions," leaked in a January 2024 commit that was likely accidental. That App had 304 installations at the time, including organizations like Civica and Sierra Nevada Corp, with permissions to modify repository content and administer organizations. Its maintainer rotated the key promptly once notified.
What an egress-proxy model would — and wouldn’t — have changed
It’s worth being precise here, because GitHub App keys don’t behave like a typical bearer token you'd inject into an outbound Authorization header. The raw RSA private key is used to sign a JWT locally, inside whatever process holds the key — a CI job, a server, a developer’s laptop. That signing step is what makes this credential class awkward to protect: the code doing the signing needs the actual key material in memory or on disk, at least momentarily.
That said, the signing operation itself doesn’t have to happen inside the same process that runs your application or CI pipeline. In an architecture where the private key lives only inside a dedicated secrets/proxy layer — the model KnoxCall's egress-gateway approach is built around — the signing happens server-side, and only the resulting short-lived installation token (already capped around an hour by GitHub) is ever handed to the runner, the build agent, or the developer's shell. No .pem file sits in a repo checkout, an env var, or a CI log for someone to commit or scrape.
Applied to these two incidents specifically: the CDC key leaked because it ended up committed inside a CDCGov repository, and the BuildBuddy key leaked inside the company’s own repository. In both cases, a human or a process had the raw key file somewhere reachable enough to accidentally check it into version control. If the raw key had never left a signing service in the first place — if engineers only ever received the ephemeral installation token their code actually needed — there would have been no private key sitting on disk to commit by mistake.
What an egress-proxy model would not have done is retroactively un-leak the Crusher.dev key that had been sitting in a public commit since 2020, or make GitHub start expiring App keys automatically. Architecture changes what's exposed to the developer going forward. It doesn't substitute for actually revoking a key once it's out, and GitGuardian's whole point stands regardless of architecture: someone still has to look.
What to actually do this week
- Inventory every GitHub App your org owns or has installed, and confirm who currently holds each private key file. Most teams can name their PATs; far fewer can name their Apps.
- Rotate any App key that has ever touched a laptop, a CI log, or a committed file — even briefly, even in a private repo. A key that was exposed for ten minutes in 2022 is functionally identical to one exposed for ten minutes today.
- Stop distributing the raw
.pemfile to CI runners or developer machines where you can avoid it. Generate installation tokens through a signing service that never releases the private key itself, and give runners only the short-lived token. - Extend secret scanning to RSA-format keys, not just token-shaped strings. GitGuardian's data set specifically included keys sitting in repositories the App's own maintainer didn't own — forks and mirrors carry credentials along with the code.
- Treat GitHub App permissions as you would cloud IAM roles. An App with organization-admin, runner-admin, and workflow-control scopes is not a convenience integration; it's a standing grant of infrastructure control.
For a broader look at why secrets keep ending up in places they shouldn't, see our related coverage on credential attacks no checklist can stop and on why secrets managers alone can't protect keys that still reach the process.