poisoned-image-executes-as
A workload poisoned via a registry image push runs as its identity, so the pusher executes as that identity.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanModifyCode {'consumer': None}
{'consumer': None} ExecutesAs {'identity': None}
where
node_type(?consumer) in [Compute,
ContainerService, ContainerTask, KubernetesWorkload, Container, VirtualMachine, ServerlessFunction, BuildWorker]
node_class(?identity) == Identity
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | ?principal |
| target | ?identity |
| conditions | image_pulled |
| state logic | inherit weakest from the contributing CanModifyCode (rule 1a) and ExecutesAs: ACTIVE only when the poison actually reaches the running workload (rule 1a ACTIVE — mutable tag re-pulled); CONDITIONAL(image_pulled) when the pull/ redeploy is not yet established; BLOCKED when the consumer is digest-pinned so rule 1a did not fire ACTIVE. Never stronger than the CanModifyCode input. |
Narrative
{principal.name} controls the code {consumer.name} runs (via a poisoned registry image), and {consumer.name} executes as {identity.name}; running the poisoned image therefore executes attacker code as {identity.name}.
Raw rule rules/derived/container-chains.yaml
id: poisoned-image-executes-as
emits: CanExecuteAs
description: A workload poisoned via a registry image push runs as its identity, so the pusher executes
as that identity.
applies_to:
- '*'
match:
- - principal: null
- CanModifyCode
- consumer: null
- - consumer: null
- ExecutesAs
- identity: null
where:
- node_type(?consumer) in [Compute, ContainerService, ContainerTask, KubernetesWorkload, Container, VirtualMachine,
ServerlessFunction, BuildWorker]
- node_class(?identity) == Identity
emit:
source_type: Identity
target_type: Identity
source: ?principal
target: ?identity
permissions: []
conditions:
- image_pulled
state_logic: "inherit weakest from the contributing CanModifyCode (rule 1a) and ExecutesAs: ACTIVE only\
\ when the poison actually reaches the running workload (rule 1a ACTIVE \u2014 mutable tag re-pulled);\
\ CONDITIONAL(image_pulled) when the pull/ redeploy is not yet established; BLOCKED when the consumer\
\ is digest-pinned so rule 1a did not fire ACTIVE. Never stronger than the CanModifyCode input."
confidence: min(contributing_confidences)
derived_from:
- ?principal CanModifyCode ?consumer
- ?consumer ExecutesAs ?identity
false_positive_note: "This dedupes with can-execute-as execute-as-via-code-modify on (CanExecuteAs,\
\ ?principal, ?identity) \u2014 SAME collapse, container/supply-chain framing; emit once, 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.\
\ Carries rule 1a's image_pulled gate: a digest-pinned or never-re-pulled consumer does not yield\
\ an ACTIVE execute-as. Only emit for ?consumer whose ExecutesAs is a real, mounted execution identity\
\ (task role / pod SA / WI), not a placeholder."
narrative: '{principal.name} controls the code {consumer.name} runs (via a poisoned registry image),
and {consumer.name} executes as {identity.name}; running the poisoned image therefore executes attacker
code as {identity.name}.'