gcp-dataflow-template-poison
Writing to the GCS object that serves as a Dataflow Flex or Classic Template poisons the next job created from that template; the job runs the attacker-controlled pipeline code as the worker SA.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?templateObject) ==
ObjectStorage # GCS object
ContainsResourceReference edge is confirmed by evidence (job's gcsPath / containerSpecGcsPath field references the specific template object)
?job is a google.dataflow.Job (or job template definition) that reads the template
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | <principal> |
| target | <Dataflow job (BatchJob) created from the template> |
| permissions | storage.objects.create |
| conditions | iam_permission trigger_exists |
| state logic | CONDITIONAL(trigger_exists): the template is poisoned, but the malicious code only executes when a new job is created from that template (dataflow.jobs.create reading the GCS path). ACTIVE when a job-creation event is confirmed to use this template imminently or on a schedule. Inherits from CanWriteData on the template object; BLOCKED if VPC Service Controls prevent writes to the GCS path. |
Narrative
{principal.name} can overwrite the Dataflow template GCS object {templateObject.name} (storage.objects.create) referenced by job {job.name}; the next job created from that template will run the attacker-controlled pipeline code as the worker service account.
Raw rule rules/derived/gcp/dataflow.yaml
id: gcp-dataflow-template-poison
emits: CanModifyCode
description: Writing to the GCS object that serves as a Dataflow Flex or Classic Template poisons the
next job created from that template; the job runs the attacker-controlled pipeline code as the worker
SA.
match:
- - ?principal
- CanWriteData
- ?templateObject
- - ?templateObject
- ContainsResourceReference
- ?job
where:
- 'node_type(?templateObject) == ObjectStorage # GCS object'
- ContainsResourceReference edge is confirmed by evidence (job's gcsPath / containerSpecGcsPath field
references the specific template object)
- ?job is a google.dataflow.Job (or job template definition) that reads the template
emit:
source_type: Identity
target_type: Compute
source: <principal>
target: <Dataflow job (BatchJob) created from the template>
permissions:
- storage.objects.create
conditions:
- iam_permission
- trigger_exists
state_logic: 'CONDITIONAL(trigger_exists): the template is poisoned, but the malicious code only executes
when a new job is created from that template (dataflow.jobs.create reading the GCS path). ACTIVE when
a job-creation event is confirmed to use this template imminently or on a schedule. Inherits from
CanWriteData on the template object; BLOCKED if VPC Service Controls prevent writes to the GCS path.'
confidence: min(contributing_confidences) * 0.85
derived_from:
- <CanWriteData edge_id for ?principal -> ?templateObject>
- <ContainsResourceReference edge_id for ?templateObject -> ?job (must be evidence-confirmed)>
false_positive_note: "STRICT: Only emit when (1) the ContainsResourceReference edge is CONFIRMED by\
\ the collector (the job or job template definition explicitly references the specific GCS path in\
\ its gcsPath / containerSpecGcsPath field), (2) the write is to the SPECIFIC GCS path that a job\
\ references, not to an arbitrary bucket. Write to an unrelated bucket is not this edge. The template\
\ poisoning only takes effect on jobs created AFTER the poisoning \u2014 existing running jobs unaffected.\
\ Without confirmed link between template GCS path and job (often unavailable before job creation),\
\ do not emit. This pattern is supply-chain injection; prefer citation of cicd-chains for the narrative."
narrative: '{principal.name} can overwrite the Dataflow template GCS object {templateObject.name} (storage.objects.create)
referenced by job {job.name}; the next job created from that template will run the attacker-controlled
pipeline code as the worker service account.'