In Google Cloud, Service Accounts (SAs) are the “user” that runs automations, workloads, and pipelines. In the documentation they usually appear as a clean and controlled component: an account is created, it is given a minimal role, it is consumed from a managed service, and that’s it. In companies, real behavior is different: SAs survive for years, accumulate permissions by inheritance, and authenticate with keys that end up outside the expected perimeter.
This article does not repeat the official example; it focuses on the risks that appear when operational pressure, unclear ownership, and organizational changes come into play. The goal is for you to recognize early signals and apply guardrails that work with real teams and processes.
Real attack surface: when long-lived keys become “permanent credentials”
The most repeated risk outside the lab is a Service Account JSON key treated like just another secret, without effective expiration and with multiple copies. It is generated “to unblock a deployment,” stored in a secrets manager or in a CI repository, and over time it ends up duplicated in multiple places: development environments, self-hosted runners, backups, and even workstations. The key does not expire on its own; the risk grows with every copy and with every system that touches it.
In real incidents, the vector is usually not a “sophisticated intrusion” into GCP, but indirect exfiltration: a CI job with verbose logs, a bucket with backups accessible by an inherited role, or a repository that was made public by mistake. Once an attacker obtains the key, they do not need to pivot through the console: they can use the API from anywhere, as if they were the legitimate workload.
- Invisible copies in build systems: it is common for the key to end up in environment variables, pipeline artifacts, or caches. The problem is not only the secret, but the number of systems with implicit access.
If your CI allows downloading artifacts or inspecting historical logs, a leaked key does not disappear “by fixing the pipeline”: you have to assume compromise until you revoke it and audit usage.
- Reuse across environments: using the same SA/key for dev, pre-prod, and prod is a common shortcut. The real impact is that a breach in dev becomes access to prod with the same identity.
In organizations with multiple teams, this reuse is often justified by speed. The cost comes later: investigating traces and attribution becomes difficult and the blast radius is unnecessarily large.
Rotation that doesn’t happen: the operational debt that becomes an incident
The lack of key rotation is usually not a conscious decision, but chronic “there’s no time.” No one wants to touch a key that feeds critical processes if there is no inventory, no tests, and no maintenance window. Result: keys that are years old, with no clear owner, deployed in places that no one remembers anymore. When there is a finding (for example, a secret detection in a repository), the organization discovers that it cannot rotate without breaking production.
In companies, the clearest symptom of this risk is fear of revocation. If revoking a key “could take down billing” or “break integrations with third parties,” the problem is no longer technical: it is governance and operational design. Also, the older the key is, the more likely it is to have been copied to systems that are not under the same control (vendors, consultancies, legacy integrations).
How to do it in practice: prepare rotation without stopping the business. The usual sequence that works is a double key with a coexistence window. Create a second key, deploy the change to consumers, validate usage, and only then revoke the previous one. In GCP, validate at least these pieces before deleting:
- Consumer inventory: identify which jobs, repos, CI/CD systems, integrations, and VMs use the key. Without this, rotating is a lottery and tends to be postponed.
- Trace-based validation: review in Cloud Logging/Audit Logs the SA’s principalEmail and the pattern of API calls after the change. The success signal is not “the deployment passed,” it is seeing stable activity with the new key and no authentication errors.
The operational key is to treat rotation as a production change: with a window, rollback (keeping the old key for a while), and explicit verification.
Inherited roles and cumulative permissions: the “organic growth” of IAM in Service Accounts
In corporate environments, a Service Account rarely keeps the initial minimum role. Over time, different teams add permissions “to resolve a blocker,” especially when there is no clear ownership. The pattern repeats: a pipeline fails due to a specific permission, someone adds a broad role at the project level (or worse, organization), the incident is resolved, and nobody goes back to tighten. That role remains inherited for any future use of the SA, even if the SA changes purpose.
The problem is not only excessive permissions, but the combination of two things: broad permissions and exportable credentials (keys). That mix turns an SA into a durable “master token.” In incidents, it is common to find SAs with roles like editor/owner in projects because it was “faster,” or with IAM administration permissions that allow expanding access while remaining the same identity.
A very common anti-pattern is delegating to an SA permissions to “manage infrastructure” and reusing it also for runtime tasks. Infra and runtime have different risk profiles: the former may need to create resources; the latter should be very restrictive. Mixing them makes a leak in runtime have administrative impact.
- Early signal: roles assigned at the project/organization level without justification by ticket or without operational expiry.
When you cannot explain why an SA has a role, that role is debt and is usually the first place an attacker will look to expand impact.
- Early signal: an SA with IAM permissions (for example, ability to modify bindings) used by general-purpose pipelines.
This transforms an authentication failure into privilege escalation: with a single identity, it is possible to grant more access to itself or to other identities if the policy allows it.
Cross-project abuse: when an identity jumps across projects without anyone seeing it
Cross-project abuse appears when a Service Account from project A obtains permissions over resources in project B (through direct bindings or through group membership, or through organizational inheritance). In companies with many projects, this practice is used to centralize deployments or for a “platform” team to operate resources for others. It works… until the SA is compromised or reused outside its original context.
The real impact often manifests as lateral movement: an SA that was born for a microservice pipeline ends up reading secrets in another project, writing to shared buckets, or administering network resources. In an incident, this complicates containment: revoking in project A does not always cut access to B if access was granted crosswise or by inheritance. Also, attribution gets diluted: many operations in B appear as “the pipeline SA,” with no clarity on which team actually ran it.
How to do it in practice: review and cut unnecessary jumps between projects. Concrete actions that often yield quick results:
- Map cross-project bindings: enumerate where the principal (serviceAccount:…) of an SA appears outside its project. The typical finding is “we didn’t know this SA had access to that project.”
As soon as you find it, force ownership conversations: if there is no owner of the access in the destination project, it is an orphan permission and must be removed or replaced with a more controlled mechanism.
- Separate identities by domain: create different SAs per project and per function (deploy vs runtime). If you need to operate cross-project, let it be an SA designed for that and with scoped and auditable permissions.
Separation reduces blast radius and improves investigation: in logs, a specific identity tells a much more accurate story than a “multi-purpose” SA.
Recommendations for corporate environments
The most damaging risks with Service Accounts in Google Cloud appear when long-lived keys are combined with low rotation, roles that grow by inheritance, and cross-project access granted for convenience. They do not fail due to lack of technical knowledge, but due to operational pressure and lack of ownership: “it works today” is prioritized and control is deferred indefinitely.
If you have to choose where to start, prioritize what reduces impact immediately: inventory and rotate keys with controlled coexistence, trim inherited roles that nobody can justify, and map cross-project access to remove orphan permissions. With these three lines, typical incidents go from “broad compromise and hard to contain” to “scoped and traceable event.”
Interested in Cloud Security?
Technical analysis, hands-on labs and real-world cloud security insights.