gcp-gcr-cross-project-push

GCR artifacts bucket grants storage.objects.create to a principal from another project - cross-project supply-chain trust allows external actors to inject images into this project's registry.

derived gcp emits CrossProjectTrust

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) principal.project_id != PROJECT (the external principal belongs to a different GCP project, e.g., a cross-project SA: X@OTHER-PROJECT.iam.gserviceaccount.com) The effective permission includes storage.objects.create on the artifacts bucket.

emit

source typeResourcePolicy
target typeExternalIdentity
source<ResourcePolicy node for the artifacts.PROJECT.appspot.com bucket IAM>
target<ExternalIdentity node for the cross-project principal>
permissionsstorage.objects.create
conditionsresource_policy
state logicACTIVE when the cross-project principal has effective storage.objects.create on the artifacts bucket (binding confirmed in bucket IAM policy) AND the external project is collected (the principal is a real SA, not a deleted member). CONDITIONAL(resource_policy) when the binding exists but the external principal's liveness or project status is unconfirmed. BLOCKED when org policy constraints/iam.allowedPolicyMemberDomains restricts the bucket IAM to project/org members only (the binding is org-policy-denied, not effective).

Narrative

The GCR artifacts bucket for project {project.name} grants storage.objects.create to {external_principal.name} from another project ({external_project}). This external principal can push or overwrite images in this registry, injecting code into workloads that pull mutable tags - a cross-project supply-chain trust that extends the attack surface to the external project.

Raw rule rules/derived/gcp/gcr.yaml

id: gcp-gcr-cross-project-push
emits: CrossProjectTrust
description: "GCR artifacts bucket grants storage.objects.create to a principal from another project \u2014\
  \ cross-project supply-chain trust allows external actors to inject images into this project's registry."
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)
- 'principal.project_id != PROJECT (the external principal belongs to a different GCP project, e.g., a
  cross-project SA: X@OTHER-PROJECT.iam.gserviceaccount.com)'
- The effective permission includes storage.objects.create on the artifacts bucket.
emit:
  source_type: ResourcePolicy
  target_type: ExternalIdentity
  source: <ResourcePolicy node for the artifacts.PROJECT.appspot.com bucket IAM>
  target: <ExternalIdentity node for the cross-project principal>
  permissions:
  - storage.objects.create
  conditions:
  - resource_policy
  state_logic: ACTIVE when the cross-project principal has effective storage.objects.create on the artifacts
    bucket (binding confirmed in bucket IAM policy) AND the external project is collected (the principal
    is a real SA, not a deleted member). CONDITIONAL(resource_policy) when the binding exists but the
    external principal's liveness or project status is unconfirmed. BLOCKED when org policy constraints/iam.allowedPolicyMemberDomains
    restricts the bucket IAM to project/org members only (the binding is org-policy-denied, not effective).
  confidence: 0.9
  derived_from:
  - cross-project member with storage.objects.create in artifacts.<PROJECT>.appspot.com bucket IAM
  false_positive_note: "Verify the external principal is live (the service account exists in the other\
    \ project, not a dangling reference to a deleted SA \u2014 a deleted SA with retained IAM binding\
    \ is a confused-deputy risk, not an active trust). Cross-organization principals (from a different\
    \ org) are higher severity than same-org, cross-project ones. Hierarchy-chains enter-project-via-cross-project-trust\
    \ rolls this up to CanEnterProject for the consuming project; do not re-derive that here."
  narrative: "The GCR artifacts bucket for project {project.name} grants storage.objects.create to {external_principal.name}\
    \ from another project ({external_project}). This external principal can push or overwrite images\
    \ in this registry, injecting code into workloads that pull mutable tags \u2014 a cross-project supply-chain\
    \ trust that extends the attack surface to the external project."
move · open · esc close