gcp-gcr-push-can-modify-code

storage.objects.create on the GCR artifacts bucket = push/overwrite a container image - code control over the registry and supply-chain poisoning of every consumer that pulls a mutable tag.

derived gcp emits CanModifyCode

match (effective permission)

{ "action": "storage.objects.create", "resource_type": "storage.googleapis.com/Bucket" }

where

resource.name matches 'artifacts.<PROJECT>.appspot.com' (the GCR artifacts backing bucket, not an arbitrary GCS bucket) The GCR endpoint (gcr.io/PROJECT) is GCS-backed (legacy GCR active, not yet migrated to Artifact Registry backend). Detect by checking artifacts bucket existence and non-empty state, or absence of an Artifact Registry gcr.io-mirror repo.

emit

source typeIdentity
target typeContainerRegistry
source<principal with effective storage.objects.create on the artifacts bucket>
target<ContainerRegistry node for gcr.io/PROJECT>
permissionsstorage.objects.create
conditionsapi_enabled
state logicACTIVE when the principal holds effective storage.objects.create on artifacts.PROJECT.appspot.com (via bucket-level IAM, project-level role binding, or org-level inheritance) AND the gcr.io endpoint is GCS-backed (legacy GCR active). CONDITIONAL(api_enabled) when the Container Registry API is not confirmed enabled (gcr.io endpoint may not be initialised). BLOCKED when a GCS IAM deny policy or VPC Service Controls perimeter denies storage.objects.create to the principal for this bucket.

Narrative

{principal.name} can push or overwrite container images in the GCR registry for project {project.name} (storage.objects.create on artifacts.{project.name}.appspot.com). Overwriting a mutable image tag injects attacker code into every workload (GKE pod, Cloud Run service, Cloud Build step, or GCE container-mode instance) that pulls that tag - a supply-chain attack with a blast radius equal to all consumers of mutable tags in this registry. container-chains produces the CanExecuteAs edges to each consumer's runtime service account.

Raw rule rules/derived/gcp/gcr.yaml

id: gcp-gcr-push-can-modify-code
emits: CanModifyCode
description: "storage.objects.create on the GCR artifacts bucket = push/overwrite a container image \u2014\
  \ code control over the registry and supply-chain poisoning of every consumer that pulls a mutable tag."
match_effective_permission:
  action: storage.objects.create
  resource_type: storage.googleapis.com/Bucket
where:
- resource.name matches 'artifacts.<PROJECT>.appspot.com' (the GCR artifacts backing bucket, not an arbitrary
  GCS bucket)
- The GCR endpoint (gcr.io/PROJECT) is GCS-backed (legacy GCR active, not yet migrated to Artifact Registry
  backend). Detect by checking artifacts bucket existence and non-empty state, or absence of an Artifact
  Registry gcr.io-mirror repo.
emit:
  source_type: Identity
  target_type: ContainerRegistry
  source: <principal with effective storage.objects.create on the artifacts bucket>
  target: <ContainerRegistry node for gcr.io/PROJECT>
  permissions:
  - storage.objects.create
  conditions:
  - api_enabled
  state_logic: ACTIVE when the principal holds effective storage.objects.create on artifacts.PROJECT.appspot.com
    (via bucket-level IAM, project-level role binding, or org-level inheritance) AND the gcr.io endpoint
    is GCS-backed (legacy GCR active). CONDITIONAL(api_enabled) when the Container Registry API is not
    confirmed enabled (gcr.io endpoint may not be initialised). BLOCKED when a GCS IAM deny policy or
    VPC Service Controls perimeter denies storage.objects.create to the principal for this bucket.
  confidence: 0.95
  derived_from:
  - effective storage.objects.create on artifacts.<PROJECT>.appspot.com (from storage.buckets.getIamPolicy
    + resourcemanager.projects.getIamPolicy + role expansion)
  false_positive_note: "(1) On projects that have migrated gcr.io to Artifact Registry backend, this rule\
    \ MUST NOT fire; storage.objects.create on the legacy bucket in those projects does not yield push\
    \ capability on the active gcr.io endpoint \u2014 the push goes through Artifact Registry (artifactregistry.repositories.uploadArtifacts).\
    \ The collection adapter must check which backend is active. (2) storage.objects.create scoped at\
    \ PROJECT level (not bucket level) grants write to ALL GCS buckets in the project, not just the artifacts\
    \ bucket. Scope the CanModifyCode to the ContainerRegistry node (artifacts bucket), not all buckets.\
    \ The linchpin (container-chains image-push-poisons-consumers) fans out only to consumers of this\
    \ registry. (3) The supply-chain reach is CONDITIONAL(image_pulled) for each consumer: only workloads\
    \ referencing MUTABLE tags (:latest, moving tags, no @sha256 digest pin) are poisoned on next pull/redeploy.\
    \ Digest-pinned consumers are NOT poisoned \u2014 container-chains enforces this gate; do not override\
    \ it here. (4) Overwriting a tag requires storage.objects.create but NOT storage.objects.delete (GCS\
    \ PUT semantics). A principal with only storage.objects.get or storage.objects.list cannot push; only\
    \ storage.objects.create is the push gate."
  narrative: "{principal.name} can push or overwrite container images in the GCR registry for project\
    \ {project.name} (storage.objects.create on artifacts.{project.name}.appspot.com). Overwriting a mutable\
    \ image tag injects attacker code into every workload (GKE pod, Cloud Run service, Cloud Build step,\
    \ or GCE container-mode instance) that pulls that tag \u2014 a supply-chain attack with a blast radius\
    \ equal to all consumers of mutable tags in this registry. container-chains produces the CanExecuteAs\
    \ edges to each consumer's runtime service account."
move · open · esc close