GCP Service Account Key Misuse in Publicly Disclosed Compromises
Leaked GCP keys grant permanent access without the friction that protects human accounts.
Summary
Leaked GCP keys grant permanent access without the friction that protects human accounts.
A GCP service account key is a JSON file. That's the whole object: a block of text that grants machine identity access to whatever the account is scoped to, with no expiry date attached unless someone bothers to set one. It gets copied into a config file, pasted into a CI pipeline, checked into a repo by accident, and from that point on it behaves exactly like the account it belongs to, no questions asked.
Compare that to a human login. Steal someone's password and you still hit multi-factor prompts, account lockouts after failed attempts, maybe a "new device detected" email. A leaked key has none of that friction. It just works, silently, for as long as it's valid, which by default is forever.
Even after someone notices the leak and deletes the key, any short-lived access token already generated from it stays alive. Google's default token lifetime runs up to 60 minutes, and under an extended lifetime policy that stretches to 12 hours. Deleting the key doesn't stop the token, because the token doesn't care. It keeps working until it expires on its own clock. Rotating a key feels like pulling the plug, but if an attacker already has a live token in hand, the plug isn't actually connected to anything for up to half a day.
What Google's own threat data shows about how often keys are the entry point
Credential problems are the main event in cloud compromise. They're the main event. More than 69% of cloud compromises traced back to credential issues, and close to 65% of alerts across organizations flagged risky use of service accounts specifically, not human accounts, not network exploits: service accounts.
Two numbers explain why that keeps happening. Sixty-eight percent of service accounts carried overly permissive roles, so most keys floating around grant more access than the job actually needs. And when Google contacted project owners after a leaked key was detected, those owners took no action in 42% of cases. Silence, in other words, is a documented and common response.
The H2 2024 Threat Horizons report from Google put weak or absent credentials at the top of the entry-vector list, responsible for close to half of all intrusions tracked. More than three in five attacker movements observed involved lateral movement after that initial entry, which tells you credential compromise doesn't stay contained to the first hop. It's the on-ramp, not the destination.
The H1 2025 report adds a more granular signal: 10.3% of alerts flagged service account keys being used from locations that didn't match expected patterns. That's a tripwire, not a confirmed breach count, but a fairly loud one. Leaked credentials made up 2.9% of initial access in that same reporting period, and Google described it as a rising vector. Leaked credentials made up a small percentage of initial access, but it's a growing trend.
The earliest documented pattern: crypto-mining as the default payoff for a stolen key
Google's November 2021 Threat Horizons Report lays out the baseline case. Out of 50 recently compromised GCP instances, 86% ended up running cryptocurrency mining software. Not data theft, not ransomware: mining. The most common thing an attacker does with a stolen cloud credential is turn it into a space heater for someone else's electric bill.
The detail that actually matters here is the speed, not the 86%. Attackers downloaded mining software within 22 seconds of compromising an account. Twenty-two seconds is not a human sitting at a keyboard deciding what to do next. That's a script, fired the moment a credential shows up somewhere scannable, probably a public GitHub repo.
That same report named the entry vectors: weak or absent passwords, leaked credentials (including keys published in public repos), vulnerable third-party software, and plain misconfiguration. None of it exotic. All of it avoidable, in theory, and none of it avoided in practice often enough.
Crypto-mining is the floor. It's what happens when an opportunistic bot finds a key and doesn't know or care what it's attached to. The more unsettling cases are the ones where someone patient found a key and did something quieter.
UC Berkeley DataHub: what a fast, well-handled key leak looks like
On May 1, 2019, a pull request against UC Berkeley's DataHub project pushed a template and some documentation to GitHub. The pull request buried live credentials, keys with push and pull access to Docker images (already public, so lower stakes there) and, more seriously, access to Kubernetes clusters.
The timeline reads almost like a drill, because in effect it was handled like one. Keys hit GitHub at 3:18 PM. Both were revoked by 3:27 PM, nine minutes later. Every in-use resource was verified uncompromised by 3:36 PM. An all-clear email went out at 3:40 PM, 22 minutes start to finish.
Google's automated scanning caught the exposed key in seconds and notified the team, not a human combing through commits. The fastest, cleanest outcome in this entire set of cases happened because a scanner outpaced the leak, not because anyone on the DataHub team was staring at their repo in real time. Speed came from automation on the detection side, and from a team that knew exactly which key to kill the moment they got the alert.
Codecov: when a tampered CI tool runs undetected for 65 days across thousands of pipelines
Now the other end of the spectrum. On January 31, 2021, an attacker exploited a flaw in how Codecov built its Docker images and used it to alter the company's Bash Uploader script, a tool that ran inside customer CI pipelines. The modified script quietly copied environment variables, tokens, keys, credentials, anything sitting in that environment, and shipped them off to a third-party server. Every single CI run that used the uploader leaked its secrets.
Codecov didn't catch it until April 1, 2021. That's roughly 65 days of a tampered tool running inside thousands of pipelines completely undetected. What got vacuumed up in that window: cloud credentials, tokens, and keys of all kinds. Basically every secret a CI pipeline touches, because that's what CI pipelines are built to touch.
Codecov had more than 29,000 customers when the breach happened, a list that included GoDaddy, Atlassian, The Washington Post, and Procter & Gamble. Investigators later said hundreds of customer networks were breached as a downstream consequence. Confirmed among the exposed: HashiCorp, Confluent, Twilio, and Rapid7, companies whose own security postures are not exactly amateur hour. The lesson isn't "these companies were careless." It's that a tampered upstream tool defeats downstream diligence almost by definition, because nobody's threat model for running a code coverage uploader includes "this might be exfiltrating my cloud keys."
The Expel GCP investigation: what audit logs reveal about the limits of least privilege
Expel published an incident report on a GCP investigation where the root cause of the initial compromise was never fully pinned down by the investigators themselves. The customer later confirmed, separately, that the key in question had been committed to a public GitHub repository, the same exposure vector, but without the swift recovery.
What makes this case worth reading closely is what happened after the attacker got in. Audit logs showed the attacker trying to create a new service account key and trying to enable the service account. Both attempts failed, because the compromised account's IAM permissions simply didn't stretch that far.
That's least privilege working as intended. The attacker held a valid, working credential and still couldn't escalate, because the account was scoped tight enough that escalation wasn't an option on the table. It's not a happy ending, detection didn't come quickly, but it's proof that permission scoping acts as a real backstop even when detection fails entirely. Compare that to the documented reality that 68% of service accounts carry overly permissive roles: this is what the remainder looks like when it actually holds.
GitHub Actions as a recurring exfiltration surface: three incidents across 2021 to 2026
CI pipelines occur repeatedly in this story because they're built to hold exactly the thing attackers want: cloud credentials, sitting in an environment that legitimate workflows need to reach. Three separate incidents on GitHub Actions specifically make the pattern impossible to write off as a one-time fluke.
The tj-actions/changed-files incident involved malicious code running a Python script that dumped memory from the GitHub Actions Runner Worker process, searching for anything that looked like an AWS or GCP key, a GitHub personal access token, an npm token, or an RSA private key. Whatever it found got written into repository logs, double base64-encoded. In public repositories, that meant anyone could download the log and decode the secrets themselves, no hacking required past that point. GitHub pulled the Action on March 15 and worked to restore a clean version once the offending commits were stripped out.
The Megalodon supply-chain attack worked at a different scale entirely: automated commits poisoned 5,561 GitHub repositories, injecting malicious GitHub Actions workflows. More than 5,700 malicious commits went out in a six-hour window. On infected machines, the payload grabbed everything: CI environment variables, AWS credentials, GCP access tokens, Azure credentials, SSH keys, Docker and Kubernetes configs, API keys, database connection strings, GitHub Actions tokens, GitLab CI/CD tokens. A shopping list of every secret a modern software team keeps lying around.
And in Google's own AI Agent Development Kit samples repository, a flaw meant a single pull request could run attacker code on Google's own CI infrastructure. Successful exploitation would have handed the attacker roles/owner on the associated Google Cloud project, the top permission tier GCP offers. The target here wasn't the application code. The target here wasn't the application code, and it never is. The pipeline is the target, because the pipeline is where the credentials live.
Keys embedded in shipped artifacts: the mobile app and npm package cases
A different failure mode: the key isn't leaked once, it's shipped, baked directly into something that gets installed on thousands of devices.
In one documented case, a live GCP service account key was found embedded inside a mobile application. The key carried excessive permissions within the project. It got fixed within four days of the report going in, which sounds fast, and is fast, but only because a researcher happened to go looking. Without that outside pair of eyes, the key sits inside every copy of the app already installed on every phone that downloaded it, and locking down a repo can't revoke access, since the app is something else entirely, distributed to every device rather than held in one place. It's already out the door.
The Bitwarden CLI npm incident followed a similar shape through a different channel. Attackers published a package, @bitwarden/cli@2026.4.0, containing a credential-stealing payload aimed at cloud provider tokens (GCP keys among them), developer platform tokens like GitHub PATs and npm publish tokens, SSH keys, shell history, environment variables, and AI tooling configuration. Anyone who ran npm install and pulled that version got the payload along with the package.
A key sitting in a private repo can be contained by restricting who can read the repo, unlike an artifact embedded elsewhere. A key baked into a mobile binary or bundled into an npm package is distributed at install scale the moment it ships, and every installed copy carries it forward. Client-side artifacts are a known reconnaissance target for this reason: credentials embedded at build time travel with the artifact rather than staying in one controlled location.
The three-part failure pattern that runs through every case
Lining all these incidents up reveals the same three-stage shape appearing every time.
Stage one is exposure. A key becomes a portable, copyable artifact through some ordinary developer workflow: a pull request, a CI script, a mobile app build, an npm publish. It's a structural byproduct of how a JSON key file moves through a normal software pipeline, not one bad actor doing one bad thing. It's a structural byproduct of how a JSON key file moves through a normal software pipeline.
Stage two is silent persistence. A static key carries no context about who's using it or why. An attacker authenticating with a stolen key looks, from the system's point of view, identical to the legitimate service that key was issued to. Codecov's roughly two-month dwell time is the clearest measurement available of how long that silence can stretch when nobody's watching the right log.
Stage three is delayed detection, and the detection method varies but the pattern doesn't: in the Expel case, investigators reconstructed the attack after the fact from audit logs. In the Codecov case, the vendor itself found the tampering, months in. In the Berkeley case, Google's automated scanner fired the alert, fast, but automated all the same. In none of these cases did the affected organization's own runtime monitoring catch the exposure while it was happening. Detection came from outside, from logs read after the fact, or from a scanner built by someone else.
Over-permissioning multiplies all three stages. With 68% of service accounts carrying broader roles than they need, every day of silent persistence in stage two is a day of broad exposure, not narrow exposure. A tightly scoped key that leaks is a bad afternoon. A broadly scoped key that leaks and sits unnoticed for 65 days is a supply-chain event with hundreds of downstream victims.
Where the GCP credential model's design choices turn individual leaks into systemic risk
No expiry by default means a key created for a one-off script and then forgotten stays valid indefinitely, with nobody's calendar reminding them it exists. Google's own Q1 2023 Threat Horizons data found project owners took no action after being contacted about a leaked key in 42% of cases. Project owners taking no action after being contacted about a leaked key in 42% of cases is closer to half, and that's the common case. That's closer to half.
Putting the pieces together, the systemic risk stops looking like bad luck and starts looking like an inevitable outcome of the credential model itself. A static file with no expiry, no MFA, and no lockout mechanism gets created faster than anyone can track it, granted broader permissions than the task requires more often than not, embedded in workflows (CI pipelines, mobile builds, npm packages) that are specifically designed to move fast and touch lots of infrastructure, and then, when it leaks, sits there working perfectly until either a scanner catches it in seconds or nobody catches it for two months.
The individual failures in each of these cases: a pull request, a tampered Docker build step, a permissive IAM role, a hardcoded string in a JavaScript bundle, are all mundane on their own. None of them requires a sophisticated adversary. What turns mundane mistakes into systemic exposure is a credential format that never expires, never asks for a second factor, and never tells anyone it's being misused until someone goes looking. The keys aren't broken. The assumption that someone will remember to revoke them is.