azure-devops-template-repo-write

Write to a shared YAML template repo/branch that a pipeline imports via 'extends: template', injecting code into all downstream pipelines that use it.

derived azure emits CanModifyCode

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} CanWrite {'template_repo_branch': None} {'template_repo_branch': None} ContainsResourceReference {'pipeline': None}

where

node_type(?pipeline) == BuildWorker ?pipeline imports ?template_repo_branch via 'extends: template' in its YAML definition ?template_repo_branch is NOT pinned to a specific commit SHA or tag in the extends: block (or its parent YAML)

emit

source typeIdentity
target typeBuildWorker
source?principal
target?pipeline
permissionsContribute (Azure Repos source namespace, vso.code_write) git push
conditionsbranch_protection
state logicACTIVE when the principal can push directly to the template branch AND the pipeline enforces no required-template-from-trusted-repo control (or the attacker's push is to the very branch the control trusts) AND the template is NOT pinned to a specific commit. CONDITIONAL(branch_protection) when a branch policy requires-reviewer or when the template import is pinned to a commit SHA/tag the attacker cannot modify. Inherit weakest from CanWrite on the template repo.

Narrative

{principal.name} can modify the shared YAML template in {template_repo_branch.name} that pipeline {pipeline.name} imports; any code injected into the template executes as the pipeline's service-connection identity on the next run.

Raw rule rules/derived/azure/devops.yaml

id: azure-devops-template-repo-write
emits: CanModifyCode
description: 'Write to a shared YAML template repo/branch that a pipeline imports via ''extends: template'',
  injecting code into all downstream pipelines that use it.'
applies_to:
- azure
match:
- - principal: null
  - CanWrite
  - template_repo_branch: null
- - template_repo_branch: null
  - ContainsResourceReference
  - pipeline: null
where:
- node_type(?pipeline) == BuildWorker
- '?pipeline imports ?template_repo_branch via ''extends: template'' in its YAML definition'
- '?template_repo_branch is NOT pinned to a specific commit SHA or tag in the extends: block (or its parent
  YAML)'
emit:
  source_type: Identity
  target_type: BuildWorker
  source: ?principal
  target: ?pipeline
  permissions:
  - Contribute (Azure Repos source namespace, vso.code_write)
  - git push
  conditions:
  - branch_protection
  state_logic: ACTIVE when the principal can push directly to the template branch AND the pipeline enforces
    no required-template-from-trusted-repo control (or the attacker's push is to the very branch the control
    trusts) AND the template is NOT pinned to a specific commit. CONDITIONAL(branch_protection) when a
    branch policy requires-reviewer or when the template import is pinned to a commit SHA/tag the attacker
    cannot modify. Inherit weakest from CanWrite on the template repo.
  confidence: 0.85
  derived_from:
  - ?principal CanWrite ?template_repo_branch
  - ?template_repo_branch ContainsResourceReference ?pipeline
  false_positive_note: "Fan-out impact can be large (one template repo write reaches many pipelines).\
    \ Verify the template import path is actually reachable (the pipeline's extends: block references\
    \ the exact branch the principal can write). A 'Require template from trusted repo' enforcement control\
    \ on the PIPELINE (not the template repo) can block using an untrusted template \u2014 but cannot\
    \ block modifications to the TRUSTED template itself. Honor branch policies on the template repo.\
    \ If the template import pins to a commit SHA or tag (rather than a branch), the CanModifyCode is\
    \ CONDITIONAL(branch_protection) because updates to that commit require the attacker to modify the\
    \ extends: block itself (which requires Edit pipeline definition, not just template-repo write)."
  narrative: '{principal.name} can modify the shared YAML template in {template_repo_branch.name} that
    pipeline {pipeline.name} imports; any code injected into the template executes as the pipeline''s
    service-connection identity on the next run.'
move · open · esc close