azure-swa-token-enables-runner-code-modify
A holder of the SWA deployment token can modify the build runner's deployment when a runner with a service identity exists.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?token is the SWA deployment token of ?swa (SWA
APIKey returned by listSecrets/action)
node_type(?swa) == ApplicationPlatform # Microsoft.Web/staticSites
?runner exists and is a CI build worker (BuildWorker) that the SWA pipeline runs on, with ExecutesAs(?runner, ?identity) satisfied
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
{'principal': None}
CanTrigger {'runner': None}
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?runner |
| conditions | trigger_exists |
| state logic | ACTIVE when the token can be used to inject definitions the runner compiles/deploys AND the attacker can also trigger the runner (matched(optional[0])), OR a pre-existing trigger (branch webhook, schedule, PR build) will execute the modified content. CONDITIONAL(trigger_exists) when the token is available but the attacker cannot ensure the runner executes the injected definitions. Inherit weakest state with contributing CanReadSecret: a CONDITIONAL/BLOCKED CanReadSecret yields at most CONDITIONAL/BLOCKED. BLOCKED if a deny assignment covers listSecrets/action or the runner has no ExecutesAs. |
Narrative
{principal.name} holds the SWA deployment token of {swa.name} and can inject deployment definitions into the build runner {runner.name} (via direct SWA CDN deploy API), modifying what the runner compiles/deploys; cicd-chains rolls up the CanExecuteAs consequence for the runner's service identity {identity.name} when a run path exists.
Raw rule rules/derived/azure/staticwebapps.yaml
id: azure-swa-token-enables-runner-code-modify
emits: CanModifyCode
description: A holder of the SWA deployment token can modify the build runner's deployment when a runner
with a service identity exists.
applies_to:
- azure
match:
- - principal: null
- CanReadSecret
- token: null
where:
- ?token is the SWA deployment token of ?swa (SWA APIKey returned by listSecrets/action)
- 'node_type(?swa) == ApplicationPlatform # Microsoft.Web/staticSites'
- ?runner exists and is a CI build worker (BuildWorker) that the SWA pipeline runs on, with ExecutesAs(?runner,
?identity) satisfied
optional:
- - principal: null
- CanTrigger
- runner: null
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?runner
permissions: []
conditions:
- trigger_exists
state_logic: 'ACTIVE when the token can be used to inject definitions the runner compiles/deploys AND
the attacker can also trigger the runner (matched(optional[0])), OR a pre-existing trigger (branch
webhook, schedule, PR build) will execute the modified content. CONDITIONAL(trigger_exists) when the
token is available but the attacker cannot ensure the runner executes the injected definitions. Inherit
weakest state with contributing CanReadSecret: a CONDITIONAL/BLOCKED CanReadSecret yields at most
CONDITIONAL/BLOCKED. BLOCKED if a deny assignment covers listSecrets/action or the runner has no ExecutesAs.'
confidence: 0.8
derived_from:
- ?principal CanReadSecret ?token (azure-swa-list-secrets)
- 'cicd-chains: cicd-modify-buildspec-executes-as'
false_positive_note: "A direct/out-of-band CDN content push using this token (without a build runner)\
\ is a static-content modification only \u2014 not CanModifyCode against a BuildWorker, and NOT fed\
\ to cicd-chains. Only emit when a runner with ExecutesAs exists. If the pipeline uses GitHub-hosted\
\ runners with no persistent identity (OIDC/WIF only), the runner identity is the GitHub Actions OIDC\
\ subject; federation-chains handles that separately. cicd-chains produces the CanExecuteAs roll-up\
\ for the runner's service identity."
narrative: '{principal.name} holds the SWA deployment token of {swa.name} and can inject deployment
definitions into the build runner {runner.name} (via direct SWA CDN deploy API), modifying what the
runner compiles/deploys; cicd-chains rolls up the CanExecuteAs consequence for the runner''s service
identity {identity.name} when a run path exists.'