Every Cloud Build run executes code from a repository with the permissions of a Google Cloud service account, so whoever can start or change a build can borrow that account's access. That is the root of most Cloud Build privilege escalation findings: a user with cloudbuild.builds.create, or a contributor who can open a pull request that triggers a build, ends up acting as an identity that can deploy, read secrets or push images. This guide explains how Cloud Build chooses its service account, which roles let people run builds as another identity, how triggers, approvals and private pools reduce exposure, how to use Secret Manager safely, and how to produce and verify SLSA provenance. It is for platform engineers, cloud security teams and auditors running Cloud Build in production. Google Cloud IAM fundamentals are covered in the Google Cloud IAM and Service Accounts Guide.
This is an independent guide from NHI Mgmt Group, based on Google Cloud's public documentation as of November 2026. It is not sponsored or reviewed by Google Cloud. Product features change, so confirm settings against Google Cloud's current documentation.
Key takeaways
- The build service account is the blast radius. Builds run as a service account; choose a dedicated, narrowly scoped user-specified account per pipeline rather than a default account.
- cloudbuild.builds.create is a privilege escalation path. Google states that Cloud Build Editor, Project Editor and Project Owner can run builds as the legacy service account, so access to submit builds equals access to that account's permissions.
- Repository write access is build access. Anyone who can change the code or config being built changes what runs, even without project access; for GitHub pull request triggers, users with read access can submit a pull request that may execute a build.
- Approvals gate sensitive triggers. A trigger marked as requiring approval holds builds as pending until a Cloud Build Approver approves or rejects them.
- Provenance needs requestedVerifyOption VERIFIED. Cloud Build generates signed provenance at SLSA Build level 3 for artifacts pushed to Artifact Registry through the images field, which you can verify with slsa-verifier or Binary Authorization.
How identity and access work in Google Cloud Build
Cloud Build has two kinds of principals. Humans and automation call the Cloud Build API to create triggers, submit builds and approve them; IAM roles decide what they can do. Each build then runs as a service account, and every gcloud, Docker or API call in a build step uses that account's credentials. A third, Google-managed identity, the Cloud Build service agent, performs actions on behalf of the service, for example creating tokens when a build uses a service account from another project.
Which service account a build uses
| Account | Identifier | When it is used | Security notes |
|---|---|---|---|
| Cloud Build legacy service account | [email protected] | Default in projects whose first build ran before the May and June 2024 change, unless org policy says otherwise | Google owned; holds roles/cloudbuild.builds.builder; cannot generate ID tokens; triggers use it by default where enabled |
| Compute Engine default service account | [email protected] | Default for builds submitted through the API or gcloud in projects created after the change | Shared with Compute Engine workloads; older projects may have granted it the Editor role |
| User-specified service account | projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT | Set in the build config serviceAccount field or chosen on the trigger | Google's recommended option; logs must go to a user-owned bucket or Cloud Logging |
Three organisation policy constraints control the defaults: constraints/cloudbuild.disableCreateDefaultServiceAccount (enforced by default, disables the legacy account in new projects), constraints/cloudbuild.useBuildServiceAccount and constraints/cloudbuild.useComputeServiceAccount. Google also recommends keeping iam.automaticIamGrantsForDefaultServiceAccounts enabled, which stops Compute Engine and App Engine default accounts created on or after 3 May 2024 from receiving the Editor role. Review older projects, where the Compute Engine default account may still hold Editor and is now used for builds.
User-specified service accounts
- Config file: set serviceAccount, then logging to CLOUD_LOGGING_ONLY, or GCS_ONLY with a logsBucket. Google-owned log buckets are not allowed, and a log bucket should not have a retention policy that blocks writes.
- Triggers: choose the account on the trigger (console or --service-account). A serviceAccount in the build config is ignored for trigger-invoked builds, so review the trigger, not just the YAML.
- Cross-project accounts: blocked while iam.disableCrossProjectServiceAccountUsage is enforced, which it is by default. Allowing it means disabling enforcement on the account's project and granting roles/iam.serviceAccountTokenCreator to the Cloud Build service agent of the building project, so treat it as an exception with an owner.
- Service-to-service calls: the legacy account cannot generate ID tokens, so invoking Cloud Run or App Engine from a build needs a user-specified account.
Admin roles and privileged access
Map Cloud Build roles to duties and remember that roles which can create builds also confer the build account's power.
| Role ID | Title | What it allows | Privilege note |
|---|---|---|---|
| roles/cloudbuild.builds.editor | Cloud Build Editor | Complete control over Cloud Build resources | Can run builds as the legacy account and change whether a trigger requires approval |
| roles/cloudbuild.builds.viewer | Cloud Build Viewer | Read-only access | Can still read build logs, so keep secrets out of logs |
| roles/cloudbuild.builds.approver | Cloud Build Approver | Approve or reject pending builds | Separate from editors to keep two-person control |
| roles/cloudbuild.connectionAdmin | Cloud Build Connection Admin | Manage repository connections | Controls which source hosts feed builds |
| roles/cloudbuild.tokenAccessor | Cloud Build Token Accessor | Access read-only and read/write repository tokens | Read/write source tokens allow code changes; prefer roles/cloudbuild.readTokenAccessor where writes are not needed |
| roles/cloudbuild.workerPoolOwner / workerPoolUser | WorkerPool Owner / User | Full control of, or run builds in, private pools | Pool owners change network reach |
- Escalation by submission: a principal with cloudbuild.builds.create and access to a powerful build account can do anything that account can do. Google names Cloud Build Editor, Project Editor and Project Owner as roles that let users run builds as the legacy account. Remove basic roles from developers and grant Cloud Build Editor only to pipeline administrators.
- Escalation by trigger edit: UpdateBuildTrigger can switch the service account, the source branch or the approval flag. Alert on it.
- Using the account: choosing a user-specified account for a build requires the iam.serviceAccounts.actAs permission on that service account. Grant it through roles/iam.serviceAccountUser on the specific account, not at project level. See the Top 10 GCP IAM Mistakes.
Integrations, API tokens and service accounts
Triggers and source connections
Triggers connect Cloud Build to source hosts such as GitHub, GitLab and Bitbucket through host connections whose tokens Cloud Build stores and exposes only to the token accessor roles. Controls that matter:
- Pull request execution: Google warns that on GitHub pull request triggers any user with read access can open a pull request that may execute a build. Require approval (or the comment control options Google documents for GitHub App triggers) on pull request triggers, and never attach a privileged service account or deploy secrets to them. See the OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide.
- Approval required: tick Approval on the trigger (gcloud --require-approval). Builds wait as pending until approved with gcloud builds approve or rejected; pending builds older than two months cannot be acted on. Use a notifier filtered on PENDING so approvers see requests.
- Allowed integrations: constraints/cloudbuild.allowedIntegrations limits which source hosts can interact with Cloud Build and overrides triggers and approvals.
- Branch and file filters: included and ignored file globs decide what starts a build; they are a convenience, not a security boundary.
Secrets
Use Secret Manager through the availableSecrets block: each secretManager entry names a versionName and an env variable, steps list it in secretEnv, and commands reference it with a $$ prefix inside args. Grant roles/secretmanager.secretAccessor on each secret to the build's service account only. Google specifically advises against using the legacy service account here, because anyone who can run a trigger using that account gains the secret at build time. Never echo secrets in steps, since build logs are readable by Cloud Build Viewer. Background on the secret store is in the Secrets Management Guide.
Private pools
Private pools are dedicated workers in a Google-owned service producer network. Google documents that they can peer with your VPC to reach private resources, can be configured without public IPs and with or without public internet access, support VPC Service Controls, and offer up to 64 machine types. Use them when builds touch private artifact stores, databases or clusters, or when you need a perimeter around build egress. See the VPC Service Controls Guide.
Provenance and supply chain integrity
Set options.requestedVerifyOption to VERIFIED and push images through the images field to Artifact Registry; builds then succeed only if provenance is generated. Google states the provenance meets SLSA level 3 assurance. Provenance is produced for container images and for Go, Maven, Python and npm artifacts uploaded to Artifact Registry, and not when you push with an explicit docker push step. View it with gcloud artifacts docker images describe --show-provenance or in the Security insights panel, and verify with slsa-verifier v2.1 or later. The provenanceSigner signature is DSSE conformant and verifiable in Binary Authorization, which lets you block deployment of images that Cloud Build did not build from the expected source. See the SLSA and Build Provenance Guide and the Google Artifact Registry Security guide.
Logging and detection
Cloud Build writes Admin Activity audit logs for methods requiring ADMIN_WRITE permissions, filterable in Logs Explorer with protoPayload.serviceName="cloudbuild.googleapis.com". Route them with an organisation sink, as described in the Google Cloud Audit Logs and Log Sinks Guide.
| Event | Audit log method | What to look for |
|---|---|---|
| Build submitted | CreateBuild | Manual builds by humans outside pipelines, builds naming an unexpected serviceAccount |
| Trigger created or changed | CreateBuildTrigger, UpdateBuildTrigger | Service account swaps, approval removed, branch filters widened |
| Build approved | ApproveBuild | Self-approval or approvals outside working hours |
| Secret accessed | Secret Manager Data Access logs (if enabled) | Access by build accounts not mapped to that secret |
| IAM change on build accounts | SetIamPolicy on service accounts or projects | New serviceAccountUser or tokenCreator grants |
Metrics:
- Share of triggers using a dedicated user-specified service account rather than a default account.
- Number of principals holding cloudbuild.builds.create through Cloud Build Editor or basic roles.
- Share of production deploy triggers requiring approval.
- Share of production images with VERIFIED provenance enforced at deploy by Binary Authorization.
- Pull request triggers with access to secrets or privileged accounts (target zero).
Responding to a suspected build account compromise
If a trigger, repository or build account is suspected of misuse, act in this order:
- Contain: disable the affected triggers and, if needed, the build service account itself; this stops new builds without deleting evidence.
- Scope: query CreateBuild and UpdateBuildTrigger entries for the period, list every build that ran as the account, and review their logs and the source revisions they built.
- Rotate: create new versions of every Secret Manager secret the account could access and revoke the old ones, and rotate repository connection tokens.
- Check outputs: identify images and artifacts pushed by suspect builds; images without valid provenance from the expected source should not be deployed.
- Recover: re-enable triggers with a new, narrower account and approval in place. The CI/CD Pipeline Compromise Playbook covers the wider response.
Hardening baseline
- Inventory: list triggers, their service accounts, connections, private pools and secrets referenced in availableSecrets across all projects.
- Set organisation policy: keep disableCreateDefaultServiceAccount and iam.automaticIamGrantsForDefaultServiceAccounts enforced, keep iam.disableCrossProjectServiceAccountUsage enforced except for documented exceptions, and set allowedIntegrations to approved source hosts.
- Create per-pipeline service accounts: one account per pipeline and environment, with only the roles that pipeline needs; move triggers one at a time, test in a non-production project, and roll back by reselecting the previous account on the trigger.
- Remove escalation paths: strip Project Editor and Owner from developers, restrict Cloud Build Editor, grant serviceAccountUser on individual accounts only.
- Gate production: require approval on deploy triggers and give Cloud Build Approver to a group separate from editors.
- Isolate pull request builds: separate triggers and accounts with no secrets or deploy rights for untrusted contributions.
- Move secrets to Secret Manager with per-secret accessor grants.
- Enable provenance and enforce it: VERIFIED on all image builds, Binary Authorization in audit mode first, then enforce.
- Use private pools for builds that reach private networks, with public IPs disabled where possible.
Small versus large estates: a small team can use one project, a handful of dedicated build accounts and manual approvals. Large estates should centralise build projects, use organisation policy to remove defaults everywhere, and enforce provenance at deploy time so a compromised project cannot ship unverified images.
Common misconfigurations
- Default account with Editor: projects where builds run as the Compute Engine default service account that still holds roles/editor give every build project-wide write access.
- Developers with Project Editor: basic roles include cloudbuild.builds.create, so developers can run builds as the build account and inherit its access.
- Privileged pull request triggers: pull request builds that can read deploy secrets let an outside contributor run code that prints them. Secrets leak quickly once untrusted code runs in a pipeline, so keep deploy secrets out of pull request builds.
- Secrets in substitutions or logs: putting tokens in substitution variables or echoing them exposes them to anyone with Cloud Build Viewer.
- Provenance generated but never checked: enabling VERIFIED without Binary Authorization or slsa-verifier at deploy adds metadata but no control.
- Trigger config trusted over trigger settings: reviewers approve a serviceAccount in YAML that the trigger ignores, while the trigger itself runs as a broader account.
Framework mapping
| Cloud Build control | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | Threat addressed |
|---|---|---|---|
| Dedicated least-privilege build accounts | AC-6, AC-2 | PR.AA-05 | ATT&CK T1078.004 Cloud Accounts |
| Approvals and separate approvers | CM-3, AC-5 | PR.PS-01 | OWASP CICD-SEC-1 Insufficient Flow Control |
| Secret Manager with per-secret grants | IA-5, SC-28 | PR.AA-01 | ATT&CK T1552 Unsecured Credentials |
| Provenance and Binary Authorization | SR-4, SI-7, SR-11 | PR.PS-01 | ATT&CK T1195.002 Compromise Software Supply Chain |
| Private pools and perimeters | SC-7 | PR.PS-01 | Build egress and data exfiltration |
| Audit log routing and alerting | AU-2, AU-6 | DE.CM-01 | OWASP CICD-SEC-10 Insufficient Logging |
Practitioner checklist
- Identify which default service account each project's builds use, and remove Editor from any default account that runs builds.
- Assign a dedicated user-specified service account to every trigger, set on the trigger itself.
- Remove basic roles from developers and limit Cloud Build Editor to pipeline administrators.
- Grant roles/iam.serviceAccountUser per build account, never project-wide.
- Require approval on production triggers and keep approvers separate from editors.
- Run untrusted pull request builds with no secrets and no deploy permissions.
- Deliver secrets through availableSecrets with per-secret Secret Accessor grants.
- Set requestedVerifyOption VERIFIED and verify provenance at deploy with Binary Authorization.
- Enforce allowedIntegrations and keep cross-project service account use disabled by default.
- Alert on UpdateBuildTrigger, ApproveBuild and IAM changes to build accounts.
Standards and references
- Google Cloud: Cloud Build service account
- Google Cloud: Cloud Build default service account change
- Google Cloud: Configure user-specified service accounts
- Google Cloud: Cloud Build IAM roles and permissions
- Google Cloud: Gate builds on approval
- Google Cloud: Use secrets from Secret Manager
- Google Cloud: Generate and validate build provenance
- Google Cloud: Cloud Build audit logging
Related NHI Mgmt Group resources: Google Cloud IAM and Service Accounts Guide · CI/CD Pipeline Identity Security Guide · Google Cloud Organization Policy Guide · CI/CD Security Guide