Every week, somewhere in the UK public sector, a developer pushes a commit to a public GitHub or GitLab repository and accidentally ships a secret with it. An API key tucked inside a config file. A hardcoded database password that got copied from a local .env and never stripped out. An internal hostname that maps directly to a server sitting behind a government firewall. This is the UK government GitHub secrets leak problem, and despite years of tooling advances, it is nowhere near solved.
I’ve spent time crawling public repos tied to NHS trusts, local councils, and contractors who work the HMRC supply chain. What I found is not a tale of one rogue developer making a stupid mistake. It is a structural failure baked into how the public sector approaches open-source contribution and developer workflow governance.

Where the secrets are actually hiding
The obvious place to look is the current HEAD of a repository. Scan the live files, grep for AKIA (AWS access key prefix), look for ghp_ (GitHub personal access tokens), search for private key headers. Tools like TruffleHog do this automatically and surface hits within seconds. But the more interesting, and more persistent, problem lives in commit history.
Git is a version control system. Its entire point is remembering everything. When a developer realises they’ve committed a secret and deletes it in a follow-up commit, the secret does not disappear. It sits in every prior commit object. If the repository is public, anyone who clones it gets the full history. A secret deleted three years ago is still retrievable with a single git log -p piped through a grep. This is not an edge case. It is how Git works.
The pattern I see most frequently in UK public sector repos is something like this: an NHS digital team open-sources an integration project, strips the obvious secrets from the current codebase, marks the repo public, and considers the job done. The commit history, however, contains six months of development where API tokens for NHS login services, SMTP credentials for patient notification systems, and internal hostname references were present in plain text.
The methodology for responsible discovery
Before going further, let’s be clear about scope. Finding these secrets is not the same as exploiting them. Responsible security research means documenting what you find, assessing whether a secret is still live and rotatable, and reporting it to the relevant organisation before publishing anything. The NCSC’s coordinated vulnerability disclosure guidance (available at ncsc.gov.uk) covers exactly this territory, and I’d strongly encourage anyone doing this kind of research to read it first.
That said, here is a practical methodology for auditing public repositories tied to UK public sector organisations. Start with GitHub’s search syntax. Queries like org:nhsengland filename:.env or org:hmrc password surface results fast. GitLab has equivalent search capabilities on public projects. You can also use Gitrob or TruffleHog pointed at an organisation’s public repos to automate the sweep across commit history, not just current files.
Once you have hits, triage them. Not all secrets are equal. An expired OAuth token is annoying but harmless. A live AWS key with IAM permissions attached to a government contractor’s account is a different matter entirely. For anything that looks live, test carefully and minimally. A HEAD request to an internal API endpoint to confirm it resolves is very different from actually authenticating and reading data. Document everything with timestamps, repo URLs, and commit hashes.
Report to the organisation’s security contact or, if you can’t find one, via the NCSC’s reporting mechanism. Give them a reasonable deadline, typically 30 days, before any public disclosure. Most public sector teams, in my experience, are genuinely grateful when you flag this stuff properly. They just had no idea the exposure existed.
Why commit-history scrubbing keeps failing
The technically correct fix for a secret in commit history is a full rewrite of that history using tools like git filter-repo (the modern replacement for the deprecated BFG Repo Cleaner, though BFG still works fine for this specific job). This removes the secret from every commit object, produces a new commit graph, and requires a force push to the remote. Every collaborator then needs to re-clone or rebase onto the rewritten history.
That last part is where it falls apart in large organisations. A council IT department running a shared GitHub organisation might have dozens of forks, CI/CD pipelines cloning specific commit SHAs, and third-party contractors who have already cloned the repo. Rewriting history breaks all of those references. In practice, teams patch the current file, commit a deletion, and move on. The secret stays in the history because actually cleaning it up is disruptive and the team lead decides the risk is acceptable, usually without fully understanding that the history is still public.
There is also a caching problem. GitHub and GitLab cache repository data aggressively. Even if you perform a correct history rewrite and force-push, the old commit objects remain accessible via direct hash references for a period while the platform’s garbage collection catches up. And if anyone ran git clone before you cleaned the history, they have a local copy you cannot touch.
The real fix is never committing secrets in the first place. Pre-commit hooks running tools like detect-secrets or gitleaks catch them before they ever reach the remote. GitHub’s own push protection feature, available on public repositories, flags known secret patterns at push time. These tools exist. The public sector is simply not mandating their use systematically.
The structural problem behind the pattern
This connects to a broader issue I’ve written about before with UK public sector data exposure. The same culture that allows Companies House data to be weaponised for corporate identity fraud and permits DVLA lookup surfaces to be systematically scraped is present in how government development teams treat open-source repositories. Security is an afterthought bolted on after something goes wrong, not a default built into the workflow from the start.
The Cabinet Office’s Technology Code of Practice says government services should use open standards and make source code open by default where possible. That is a genuinely good principle. The problem is that “open by default” requires “secure by default” to accompany it, and the guidance on secrets management in that code is vague enough that individual teams interpret it however they like.
Contractors are a particular weak point. HMRC, for instance, uses a large ecosystem of technology vendors who build and maintain systems under contract. Those contractors sometimes maintain their own public repos as part of their wider business. Internal hostnames, staging environment credentials, and integration API keys from government systems turn up in contractor repos that have nothing on their face to suggest a government connection. The attack surface is distributed across dozens of organisations, and no single team owns visibility of all of it.
The approach taken with the NCSC’s Early Warning service shows that centralised threat monitoring infrastructure does exist in the UK, but passive monitoring of known infrastructure is very different from proactively scanning public repositories for credential exposure. That gap is where the UK government GitHub secrets leak problem lives, mostly unexamined, definitely unresolved.
What actually works
Mandate pre-commit hooks as a condition of contributing to any public-sector repository. This is not complicated to implement. A shared .pre-commit-config.yaml in every organisation’s template repository, with gitleaks as a required hook, would catch the majority of accidental commits before they happen. Pair that with GitHub push protection enabled at the organisation level, and you cut the exposure surface dramatically.
Rotate secrets on a schedule regardless of whether you think they’ve been exposed. If a key has been alive for more than 90 days in a repo with any public history, rotate it. The cost is low. The cost of leaving a live key in a historical commit that someone discovers at 2 AM on a Sunday is considerably higher.
Run regular automated scans against your own organisation’s public repos using TruffleHog or Gitleaks in CI. Treat findings as P1 incidents. And if you are a contractor building systems for government, scope your secret management practices to meet at least the same standard as the client organisation’s security policy requires, because right now many of them simply do not.
The tooling to solve this problem has existed for years. The will to enforce it systematically across the public sector is what’s missing.

Leave a Reply