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.

derived gcp emits CanModifyCode

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 typeIdentity
target typeContainerRegistry
source<principal>
target<Artifact Registry Docker/OCI repository in scope>
permissionsartifactregistry.tags.update
state logicACTIVE 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.'
move · open · esc close