Inside GitHub’s nine-month cleanup of 20,000+ exposed secrets
When GitHub’s security team first ran the company’s own secret scanning tool across its internal estate, the results were sobering: more than 20,000 secrets scattered across over 15,000 repositories. The raw number looked overwhelming, but the team soon realized that the real work was not in the count—it was in separating genuine risk from noise, finding owners, and building a repeatable remediation process. Nine months later, they hit zero open alerts.
The journey offers a practical playbook for any organization staring down a similar backlog. Here’s how GitHub approached it.
Don’t trust the raw alert count
The first insight was that 20,000 alerts did not mean 20,000 problems. A deeper dive revealed that just five repositories accounted for roughly 18,000 of those alerts—and every one of those secrets was inactive. They were test fixtures, deactivated credentials, and fake-but-valid-looking strings used in the company’s own testing of secret scanning.
That left just over 2,000 alerts that required genuine attention: potential live credentials and thousands of decisions about risk, rotation, and remediation.
Secrets live beyond code
Remediation work stretched beyond source code. GitHub found secrets in support tickets, bug bounty reports, incident notes, and wiki pages. Customers would include tokens in tickets; researchers would submit reproductions containing API requests with live credentials.
The team partnered with customer support, security incident response, and the bug bounty program to develop shared playbooks. Every workflow had to be designed to avoid creating new problems—such as opening issues or pushing commits that contained the very secrets being remediated.
Treat it as an operational backlog
The scale of the problem ruled out manual grinding by a few security engineers. Instead, GitHub treated the cleanup like any other operational queue: stop new debt, then work down the existing pile with a repeatable, measurable workflow.
Phase 1: Turn on protection everywhere
Before cleaning up existing secrets, GitHub had to prevent new ones from accumulating. Secret scanning and push protection were enabled across all enterprises and organizations via GitHub Advanced Security’s organization-level settings—no repository-by-repository rollout across 15,000 repos. The setting was enforced so teams could not quietly opt out, and push protection blocked new secrets at the source.
Phase 2: Triage by category
Alerts were broken down by repository, secret type, and age. For high-volume, low-risk alerts, GitHub developed bulk-closure criteria: if a secret lived in a dedicated test repository, had never been active, and matched a known test pattern, it could be marked resolved. Within days, roughly 18,000 alerts were closed this way.
The remaining alerts raised harder strategic questions. When a secret appears in an issue, do you edit the body and risk losing revision history, or preserve the audit trail? When a commit contains a secret, is rewriting git history worth the disruption—force-pushes that break open pull requests and invalidate commit SHAs?
Deleting unused repositories was generally not an answer. A deleted repository takes its forensic record with it, which would be essential if a secret was ever leaked or the repo compromised. The default approach: rotate the secret, archive the repository if appropriate, but keep the history.
Phase 3: Validate what’s actually live
At the time, secret scanning lacked native validity checking, so GitHub built its own narrow validation layer. The goal was to answer two questions: does this credential still work, and who needs to know about it?
For a GitHub token, a representative check might make a single authenticated request to a low-impact endpoint:
response="$(
curl -sS -w '\n%{http_code}' \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/user
)"
status="${response##*$'\n'}"
body="${response%$'\n'*}"
case "$status" in
200)
login="$(jq -r '.login // empty' <<< "$body")"
echo "token appears active for GitHub user: $login"
;;
401)
echo "token appears invalid or revoked"
;;
403|429)
echo "unable to determine validity; rate-limited or blocked"
;;
*)
echo "unable to determine validity: HTTP $status"
;;
esac
Ambiguous responses were treated as inconclusive, and the team avoided follow-on requests to repositories, organizations, or other private resources. This work required close partnership with privacy and legal teams, since even a read-only validity check touches a credential you may not own. The manual effort was later productized—validity checking is now native in GitHub secret scanning.
Phase 4: Solve the ownership problem
Knowing a credential was live was only half the battle. GitHub still had to find who could rotate it. For GitHub-issued credentials like personal access tokens, the product team surfaced metadata directly in alerts—who created the token, when, and what scopes it had—eliminating the need to use the token itself for identification.
For other credentials, ownership was harder to determine. Not all repositories had clear owners. The pain led to a broader repository ownership initiative using GitHub’s Custom Properties, plus a parallel effort to ensure every secret in the credential manager has a durable owner. As the team put it: you can’t rotate a secret if you can’t find the owner.
Phase 5: Manual triage for the long tail
Even with validation and metadata, a significant number of alerts required human judgment. For each one: what does this grant access to, has it been rotated, who owns the connected system, and what is the remediation path?
For every dismissed alert, GitHub recorded an accurate disposition—such as revoked, used in test, or false positive—alongside a comment with context like a link to a remediation issue or an approved exception.
Phase 6: Systematize and hold teams accountable
As patterns emerged, GitHub made the work scalable:
- Alerts were routed into the internal vulnerability management platform for centralized tracking and reporting.
- Playbooks were documented by secret type so teams could self-serve remediation steps.
- Notifications were automated, routing alerts to the right teams based on repository ownership.
The final piece was accountability. Secret remediation was tied to GitHub’s Engineering Fundamentals program, making it a measured security fundamental. When secret hygiene is part of engineering health, it becomes a shared responsibility across the organization.
Lessons learned
- Don’t panic at the number. The initial count was 20,000+ alerts, but 90% were not valid. The raw count is almost never the real scope of work.
- Enable and enforce everywhere, no exceptions. Partial rollouts create blind spots. Enforce scanning and push protection at the enterprise level.
- Validate before you escalate. Not every detected secret is live. Validation helps create a prioritized to-do list.
- Metadata saves hours. For GitHub credentials, secret metadata cut down detective work. For third-party providers, push for similar metadata or build your own enrichment layer.
- You can’t remediate without ownership. Invest in durable ownership infrastructure early.
- Automate the workflow after detection. Detection is only the start; routing alerts, tracking owners, and closing the loop is the operational challenge.
- Make it everyone’s problem. Security teams can’t remediate thousands of alerts alone. Tie secret hygiene to measurable fundamentals and leadership visibility.
- Document your decision framework. When rotation isn’t cleanly possible, document when it’s sufficient, when history rewrites are warranted, and when residual risk is acceptable.
What this means for you
Most of GitHub’s manual workarounds—validity checking, ownership identification, and bulk triage—are now native features in secret scanning. For teams starting today, the recommended path is straightforward:
- Enable and enforce secret scanning and push protection everywhere.
- Triage the backlog by repository and secret type; bulk-close what you can prove is noise.
- Validate what’s live before escalating.
- Route alerts to owners and track remediation like any other engineering work.



