gcp-ar-tag-update-is-code-modify
Moving a tag to a different digest (artifactregistry.tags.update) is equivalent to a code push - mutable-tag consumers that re-pull will run the new image.
match (effective permission)
{
"action": "artifactregistry.tags.update",
"resource_type": "google.artifactregistry.Repository"
}
where
node_type(?repo) in [
ContainerRegistry] # tag semantics apply to OCI/Docker repos
emit
| source type | Identity |
|---|---|
| target type | ContainerRegistry |
| source | <principal> |
| target | <Artifact Registry Docker/OCI repository in scope> |
| permissions | artifactregistry.tags.update |
| state logic | ACTIVE when the permission is effective and the repo type is Docker/OCI (tag move has no code-execution meaning for non-OCI package repos). BLOCKED if the repo has ImmutableTags: true — the tag update API call is rejected. CONDITIONAL(image_pulled) for the downstream fan-out (consumer must re-pull after the retag). |
Narrative
{principal.name} can move a Docker tag in {repo.name} (artifactregistry.tags.update) to point to an attacker-controlled image digest; any workload that re-pulls that tag will run the attacker's image, equivalent to a supply-chain push.
Raw rule rules/derived/gcp/artifactregistry.yaml
id: gcp-ar-tag-update-is-code-modify
emits: CanModifyCode
description: "Moving a tag to a different digest (artifactregistry.tags.update) is equivalent to a code\
\ push \u2014 mutable-tag consumers that re-pull will run the new image."
match_effective_permission:
action: artifactregistry.tags.update
resource_type: google.artifactregistry.Repository
where:
- 'node_type(?repo) in [ContainerRegistry] # tag semantics apply to OCI/Docker repos'
emit:
source_type: Identity
target_type: ContainerRegistry
source: <principal>
target: <Artifact Registry Docker/OCI repository in scope>
permissions:
- artifactregistry.tags.update
conditions: []
state_logic: "ACTIVE when the permission is effective and the repo type is Docker/OCI (tag move has\
\ no code-execution meaning for non-OCI package repos). BLOCKED if the repo has ImmutableTags: true\
\ \u2014 the tag update API call is rejected. CONDITIONAL(image_pulled) for the downstream fan-out\
\ (consumer must re-pull after the retag)."
confidence: 0.9
derived_from:
- 'effective permission: artifactregistry.tags.update on <repo>'
false_positive_note: "Tag update only applies to Docker/OCI repos. For Maven/npm/PyPI repos, tags.update\
\ does not exist in the same way \u2014 do not emit for non-OCI repos. Repos with ImmutableTags: true\
\ reject tag updates \u2014 emit BLOCKED. The consumer must re-pull after the retag for the poison\
\ to land; container-chains carries the CONDITIONAL(image_pulled) gate on the fan-out edge, not here."
narrative: '{principal.name} can move a Docker tag in {repo.name} (artifactregistry.tags.update) to
point to an attacker-controlled image digest; any workload that re-pulls that tag will run the attacker''s
image, equivalent to a supply-chain push.'