gcp-ar-policy-self-grant-write
A principal that can set a repo's IAM policy can self-grant roles/artifactregistry.writer and then push, reaching CanModifyCode on the repo via policy manipulation.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?repo_policy is the
ResourcePolicy of an Artifact Registry repository ?repo
node_type(?repo) in [ContainerRegistry, ArtifactRepository]
emit
| source type | Identity |
|---|---|
| target type | * |
| source | ?principal |
| target | <Artifact Registry repository whose ResourcePolicy is ?repo_policy> |
| permissions | artifactregistry.repositories.setIamPolicy artifactregistry.repositories.uploadArtifacts |
| conditions | iam_permission |
| state logic | CONDITIONAL(iam_permission): the self-grant step requires a successful setIamPolicy call followed by uploadArtifacts, a sequential two-step action. The iam_permission condition gates both steps — if either setIamPolicy or uploadArtifacts is denied by IAM deny policy, the derivation is BLOCKED. ACTIVE when the setIamPolicy CanModifyPolicy is ACTIVE and no deny shadows the uploadArtifacts permission. Inherit weakest from the contributing CanModifyPolicy. |
Narrative
{principal.name} can set the IAM policy of {repo.name} (artifactregistry.repositories.setIamPolicy), granting itself writer access, and then push a malicious image (artifactregistry.repositories. uploadArtifacts) - policy manipulation yields the same supply-chain CanModifyCode as a direct writer grant.
Raw rule rules/derived/gcp/artifactregistry.yaml
id: gcp-ar-policy-self-grant-write
emits: CanModifyCode
description: A principal that can set a repo's IAM policy can self-grant roles/artifactregistry.writer
and then push, reaching CanModifyCode on the repo via policy manipulation.
match:
- - principal: null
- CanModifyPolicy
- repo_policy: null
where:
- ?repo_policy is the ResourcePolicy of an Artifact Registry repository ?repo
- node_type(?repo) in [ContainerRegistry, ArtifactRepository]
emit:
source_type: Identity
target_type: '*'
source: ?principal
target: <Artifact Registry repository whose ResourcePolicy is ?repo_policy>
permissions:
- artifactregistry.repositories.setIamPolicy
- artifactregistry.repositories.uploadArtifacts
conditions:
- iam_permission
state_logic: "CONDITIONAL(iam_permission): the self-grant step requires a successful setIamPolicy call\
\ followed by uploadArtifacts, a sequential two-step action. The iam_permission condition gates both\
\ steps \u2014 if either setIamPolicy or uploadArtifacts is denied by IAM deny policy, the derivation\
\ is BLOCKED. ACTIVE when the setIamPolicy CanModifyPolicy is ACTIVE and no deny shadows the uploadArtifacts\
\ permission. Inherit weakest from the contributing CanModifyPolicy."
confidence: 0.9
derived_from:
- ?principal CanModifyPolicy ?repo_policy (gcp-ar-set-iam-policy)
false_positive_note: "The self-grant is a two-step action (setIamPolicy then uploadArtifacts); both\
\ steps must succeed. An IAM deny blocking setIamPolicy keeps this BLOCKED regardless of the upload\
\ permission. The CanModifyCode emitted here is the SAME capability as gcp-ar-upload-is-code-modify\
\ but reached via policy manipulation; graph deduplication should treat them as the same (CanModifyCode,\
\ ?principal, ?repo) edge \u2014 pick higher confidence, do not double-count weight. container-chains\
\ fan-out applies equally. The target_type is resolved at emit time from the repository's generic_type\
\ (ContainerRegistry for Docker/OCI, ArtifactRepository for package repos)."
narrative: "{principal.name} can set the IAM policy of {repo.name} (artifactregistry.repositories.setIamPolicy),\
\ granting itself writer access, and then push a malicious image (artifactregistry.repositories. uploadArtifacts)\
\ \u2014 policy manipulation yields the same supply-chain CanModifyCode as a direct writer grant."