I assumed private repositories were the safe ones. Everyone I've asked assumed the same thing.
GitGuardian's 2026 report says 32.2% of internal repositories contain at least one hardcoded secret, against about 6% of public ones. Roughly six times worse.
The reason isn't technical. It's that "it's internal" feels safe, so the bar drops. Nobody hardcodes a production key into a public repo because the consequence is obvious. In a private one, the consequence feels theoretical, and a key gets pasted in to unblock something on a Friday.
The number that actually worries me
Here's the one I keep coming back to.
64% of valid secrets leaked in 2022 were still not revoked when the 2026 report was published. Four years. Still valid.
So the leak isn't really the incident. The incident is that nothing happened afterwards.
That reframes the whole problem for me. I used to think of this as a detection question, catching the secret before it lands. It's mostly a follow-through question. Something gets found, somebody says they'll rotate it, and then a sprint happens.
For scale on how often this is landing: GitGuardian detected 28.65 million new hardcoded secrets in public GitHub commits during 2025, up 34% on the previous year.
Kubernetes Secrets aren't secret
Worth stating plainly because the name genuinely misleads people, and it misled me.
A Kubernetes Secret is base64 encoded, not encrypted. Anyone who can read the object can read the value. Base64 isn't a security control, it's a transport format, and you can decode it in your browser's console.
That matters more than it sounds because of what sits around it. The CNCF Security TAG estimates that 67% of Kubernetes security incidents involve credential exposure through misconfigured secrets.
And there's a specific pattern worth knowing. When a Secret is mounted as an environment variable via secretKeyRef, any process in that container can read it, and if the pod has multiple containers, all of them can. There's no auditing on that read, no expiry, nothing beyond basic RBAC on the object itself. Mounting as a file is better, because at least file permissions apply.
Environment variables also end up in places you didn't intend. Crash dumps. Debug endpoints. Log lines from a library that decided to print its config on startup.
One credential is never one credential
The lateral movement part is what makes this worse than it first looks.
GitGuardian's writeup on leaked Kubernetes credentials makes the point that a single one rarely stays in the cluster. It usually opens registry credentials, which open private container images, which frequently contain more credentials.
Their detectors found close to 2,000 new Kubernetes secret leaks on GitHub in Q1 2026 alone, and 28% were still valid at the time they were found.
The chain that gets documented in threat reports goes: credentials from a developer machine, then cloud account enumeration, then cluster access, then poisoned images to move sideways and collect more. Each step uses something the previous step handed over.
What I actually check for
The thing that used to stop me doing this properly was that it meant going repo by repo. We have seven, which is manageable. Thirty is not, and neither is doing it often enough to matter.
Vörr searches code across every connected repository at once, which is what turned this from a project into a task. I run a lexical search across the whole organisation rather than one repo at a time.
What I look for:
Assignment patterns. Things like password =, api_key =, token =, secret = with a literal on the right hand side rather than a variable or an environment lookup.
Known key prefixes. Most providers use recognisable ones, which makes them easy to match and hard to hide.
Connection strings, because credentials tend to travel inside them and nobody thinks of a database URL as a secret.
secretKeyRef in manifests, to find which pods are taking secrets as environment variables rather than files.
And config files that shouldn't be committed at all. .env, anything with credentials in the filename.
The results need reading rather than trusting. Test fixtures and example configs turn up constantly and most of them are fine. But it takes minutes instead of an afternoon, which is the only reason it happens more than once.
Rotation is the part that fails
Given that 64% figure, this is where I'd put the effort.
Rotate on discovery, not on schedule. If a secret has been in a repository, it's compromised, even if the repo is private and even if you deleted the commit. Cloning is instant and history is recoverable.
Removing it from Git isn't enough. git filter-repo or BFG will clean history, but if the value was ever pushed anywhere, assume somebody has it. Rewriting history is cleanup, not remediation.
Track what you found to a conclusion. A list of discovered secrets with no owner and no revoked date is how you end up in the 64%. The finding is the easy part.
And check Slack and Jira too. Around 28% of incidents in that report started outside a repository entirely, in collaboration tools, where somebody pasted a credential into a thread to unblock a colleague. It's still there.
The bit I got wrong
I used to think of this as a code review problem. Catch it in the PR, done.
It's really an inventory problem. You need to know what exists, where it's referenced, and whether it's still valid, and none of those are questions a code review answers. Review catches the next one. It does nothing about the ones already in there, which, statistically, is most of them.
Author note
Manjunaathaa, Associate DevOps Development Engineer at Frigga Cloud Labs.
The 64% figure changed how I think about security work generally. I'd assumed the hard part was finding things. It turns out finding things is comparatively easy and closing them out is where everything falls apart, because finding is interesting and revoking is admin.
Which probably says more about how we prioritise than about the tooling.
If your team has a rotation process that survives contact with a busy sprint, I'd genuinely like to hear how. LinkedIn.
