gcp-ar-cross-project-writer
An Artifact Registry repo IAM allow policy that grants roles/artifactregistry.writer (or uploadArtifacts) to a service account from a different GCP project establishes cross-project trust - that SA can push to this project's registry.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?repo is an Artifact Registry repository node
?repo_policy is the
ResourcePolicy of ?repo
?repo_policy has an IAM binding for a ServiceAccount from a DIFFERENT GCP project (member is serviceAccount:*@<other-project>.iam.gserviceaccount.com)
the binding role or permissions include artifactregistry.repositories.uploadArtifacts
node_type(?repo) in [ContainerRegistry, ArtifactRepository]
emit
| source type | ResourcePolicy |
|---|---|
| target type | Project |
| source | ?repo_policy |
| target | <GCP project of the external ServiceAccount> |
| permissions | artifactregistry.repositories.uploadArtifacts |
| conditions | resource_policy |
| state logic | ACTIVE when the IAM binding is present, the role includes uploadArtifacts, and no IAM deny overrides it. CONDITIONAL(resource_policy) if the binding has an IAM condition expression (e.g. a resource tag condition) that may not be satisfied. BLOCKED if a VPC-SC perimeter prevents the cross-project API call or an org policy restricts cross-project resource sharing. Inherit weakest from the binding's effective state. |
Narrative
Repository {repo.name} (project {repo.project}) grants uploadArtifacts to service account {sa.name} from project {sa.project}. A principal that can act as {sa.name} can push to {repo.name} and poison every mutable-tag workload consuming images from it in {repo.project}.
Raw rule rules/derived/gcp/artifactregistry.yaml
id: gcp-ar-cross-project-writer
emits: CrossProjectTrust
description: "An Artifact Registry repo IAM allow policy that grants roles/artifactregistry.writer (or\
\ uploadArtifacts) to a service account from a different GCP project establishes cross-project trust\
\ \u2014 that SA can push to this project's registry."
match:
- - repo: null
- HasPolicy
- repo_policy: null
where:
- ?repo is an Artifact Registry repository node
- ?repo_policy is the ResourcePolicy of ?repo
- ?repo_policy has an IAM binding for a ServiceAccount from a DIFFERENT GCP project (member is serviceAccount:*@<other-project>.iam.gserviceaccount.com)
- the binding role or permissions include artifactregistry.repositories.uploadArtifacts
- node_type(?repo) in [ContainerRegistry, ArtifactRepository]
emit:
source_type: ResourcePolicy
target_type: Project
source: ?repo_policy
target: <GCP project of the external ServiceAccount>
permissions:
- artifactregistry.repositories.uploadArtifacts
conditions:
- resource_policy
state_logic: ACTIVE when the IAM binding is present, the role includes uploadArtifacts, and no IAM deny
overrides it. CONDITIONAL(resource_policy) if the binding has an IAM condition expression (e.g. a
resource tag condition) that may not be satisfied. BLOCKED if a VPC-SC perimeter prevents the cross-project
API call or an org policy restricts cross-project resource sharing. Inherit weakest from the binding's
effective state.
confidence: 0.9
derived_from:
- 'gcp-ar-cross-project-binding (explicit: cross-project IAM binding)'
false_positive_note: "The cross-project SA must actually be usable by a principal the attacker controls\
\ \u2014 the trust edge models the repo's posture, not that the SA is necessarily compromised. Path-finding\
\ chains this CrossProjectTrust to the SA's owner project; only if a principal in that project can\
\ act as the SA (via iam.serviceAccounts.actAs or direct SA credential access) does the push capability\
\ materialize. Verify that the SA is not a Google-managed service agent (those have restricted key\
\ creation and actAs surfaces)."
narrative: Repository {repo.name} (project {repo.project}) grants uploadArtifacts to service account
{sa.name} from project {sa.project}. A principal that can act as {sa.name} can push to {repo.name}
and poison every mutable-tag workload consuming images from it in {repo.project}.