cicd-modify-buildspec-executes-as
Modify the buildspec/pipeline/runbook/template a runner executes, then run it, to execute as the runner's service identity.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?runner) in [
BuildWorker, ContainerTask, ContainerService, ServerlessFunction, VirtualMachine, KubernetesWorkload]
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
{'principal': None}
CanTrigger {'runner': None}
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | ?principal |
| target | ?identity |
| conditions | run_path |
| state logic | ACTIVE if a run path exists for the modified definition — the attacker can also trigger/start the runner (matched(optional[0])), OR the runner runs on a trigger the modification arms (push-to-branch webhook, PR build, schedule, upstream pipeline stage) so the injected code runs without further action. Otherwise CONDITIONAL(run_path): the code is planted but has no established way to run. Inherit weakest with the contributing CanModifyCode / ExecutesAs: a CONDITIONAL/BLOCKED CanModifyCode (branch protection, required review, protected pipeline, deny on the definition store) caps or blocks this edge. |
Narrative
{principal.name} can modify the definition {runner.name} executes (its buildspec/pipeline/runbook/template or the source it builds) and run it, executing attacker code as the runner's identity {identity.name}.
Raw rule rules/derived/cicd-chains.yaml
id: cicd-modify-buildspec-executes-as
emits: CanExecuteAs
description: Modify the buildspec/pipeline/runbook/template a runner executes, then run it, to execute
as the runner's service identity.
applies_to:
- '*'
match:
- - principal: null
- CanModifyCode
- runner: null
- - runner: null
- ExecutesAs
- identity: null
where:
- node_type(?runner) in [BuildWorker, ContainerTask, ContainerService, ServerlessFunction, VirtualMachine,
KubernetesWorkload]
optional:
- - principal: null
- CanTrigger
- runner: null
emit:
source_type: Identity
target_type: Identity
source: ?principal
target: ?identity
permissions: []
conditions:
- run_path
state_logic: "ACTIVE if a run path exists for the modified definition \u2014 the attacker can also trigger/start\
\ the runner (matched(optional[0])), OR the runner runs on a trigger the modification arms (push-to-branch\
\ webhook, PR build, schedule, upstream pipeline stage) so the injected code runs without further\
\ action. Otherwise CONDITIONAL(run_path): the code is planted but has no established way to run.\
\ Inherit weakest with the contributing CanModifyCode / ExecutesAs: a CONDITIONAL/BLOCKED CanModifyCode\
\ (branch protection, required review, protected pipeline, deny on the definition store) caps or blocks\
\ this edge."
confidence: min(contributing_confidences)
derived_from:
- ?principal CanModifyCode ?runner
- ?runner ExecutesAs ?identity
false_positive_note: "The judgment is (a) that CanModifyCode truly reaches the EXECUTED definition and\
\ (b) that a run path exists. Do NOT emit ACTIVE when a branch-protection / required-reviewer / protected-pipeline\
\ control gates the merge that would run the code (that CanModifyCode should itself be CONDITIONAL/BLOCKED,\
\ and this edge inherits it). Editing a definition on a branch/stage the runner never executes is\
\ not this edge \u2014 scope CanModifyCode to the ref/stage the runner actually runs. This dedupes\
\ with can-execute-as execute-as-via-code-modify on (CanExecuteAs, ?principal, ?identity) \u2014 same\
\ collapse, CI/CD framing; do not double-count weight where both fire. The escalation consequence\
\ (identity more privileged than the principal) is produced by can-execute-as / can-control off this\
\ CanExecuteAs \u2014 cite, do not duplicate. iam:PassRole / actAs that lets the runner assume a DIFFERENT\
\ privileged role is the config-identity-swap case in can-execute-as, not this rule."
narrative: '{principal.name} can modify the definition {runner.name} executes (its buildspec/pipeline/runbook/template
or the source it builds) and run it, executing attacker code as the runner''s identity {identity.name}.'