azure-devops-push-trigger-branch

Write access to the CI-trigger branch of the pipeline's source repo lets a principal inject code that the pipeline will execute on the next triggered run.

derived azure emits CanModifyCode

match

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

{'principal': None} CanWrite {'repo_branch': None} {'repo_branch': None} CanTrigger {'pipeline': None}

where

node_type(?pipeline) == BuildWorker ?repo_branch is the branch / ref filter the pipeline's CI trigger watches node_type(?repo_branch) in [ObjectStorage, GenericStorage] # Azure Repos branch modeled as concrete ObjectStorage or GenericStorage node

emit

source typeIdentity
target typeBuildWorker
source?principal
target?pipeline
permissionsContribute (Azure Repos source namespace, vso.code_write) git push to trigger ref
conditionsbranch_protection
state logicACTIVE when the principal can push directly to the trigger branch AND no branch policy requires pull-request review before merge (or requires minimum approver count that the attacker cannot satisfy alone). CONDITIONAL(branch_protection) when a branch policy (required-reviewer, minimum approvers, work-item linking, etc.) gates direct pushes — the attacker must craft a PR that passes review, which is not guaranteed. Inherit weakest from the CanWrite contributor: a CONDITIONAL CanWrite (branch policy) yields a CONDITIONAL CanModifyCode.

Narrative

{principal.name} has write access to the CI-trigger branch of the repository that pipeline {pipeline.name} builds; a push to that branch triggers a run executing the committed pipeline YAML as the service-connection identity.

Raw rule rules/derived/azure/devops.yaml

id: azure-devops-push-trigger-branch
emits: CanModifyCode
description: Write access to the CI-trigger branch of the pipeline's source repo lets a principal inject
  code that the pipeline will execute on the next triggered run.
applies_to:
- azure
match:
- - principal: null
  - CanWrite
  - repo_branch: null
- - repo_branch: null
  - CanTrigger
  - pipeline: null
where:
- node_type(?pipeline) == BuildWorker
- ?repo_branch is the branch / ref filter the pipeline's CI trigger watches
- 'node_type(?repo_branch) in [ObjectStorage, GenericStorage]  # Azure Repos branch modeled as concrete
  ObjectStorage or GenericStorage node'
emit:
  source_type: Identity
  target_type: BuildWorker
  source: ?principal
  target: ?pipeline
  permissions:
  - Contribute (Azure Repos source namespace, vso.code_write)
  - git push to trigger ref
  conditions:
  - branch_protection
  state_logic: "ACTIVE when the principal can push directly to the trigger branch AND no branch policy\
    \ requires pull-request review before merge (or requires minimum approver count that the attacker\
    \ cannot satisfy alone). CONDITIONAL(branch_protection) when a branch policy (required-reviewer, minimum\
    \ approvers, work-item linking, etc.) gates direct pushes \u2014 the attacker must craft a PR that\
    \ passes review, which is not guaranteed. Inherit weakest from the CanWrite contributor: a CONDITIONAL\
    \ CanWrite (branch policy) yields a CONDITIONAL CanModifyCode."
  confidence: 0.9
  derived_from:
  - ?principal CanWrite ?repo_branch
  - ?repo_branch CanTrigger ?pipeline
  false_positive_note: "Check branch policies on the trigger branch (GET git/repositories/{id}/refs/policies).\
    \ A policy requiring at least one reviewer or pull-request review before merge prevents a direct push\
    \ from yielding ACTIVE code control \u2014 downgrade to CONDITIONAL(branch_protection). GitHub-hosted\
    \ repos also require checking the GitHub branch protection rules separately. For pull-request triggers\
    \ (pr: config in YAML), also check fork isolation settings (azure-devops-pr-trigger-executes-as) \u2014\
    \ a PR from a fork may not have service-connection access if fork isolation is enabled."
  narrative: '{principal.name} has write access to the CI-trigger branch of the repository that pipeline
    {pipeline.name} builds; a push to that branch triggers a run executing the committed pipeline YAML
    as the service-connection identity.'
move · open · esc close