gcp-dataflow-update-contents-modify-code
dataflow.jobs.updateContents on a streaming Dataflow job lets the principal inject attacker-controlled transforms into the live pipeline; updated code runs immediately as the worker SA.
match (effective permission)
{
"action": "dataflow.jobs.updateContents",
"resource_type": "google.dataflow.Job"
}
where
?job is a STREAMING job (job type == JOB_TYPE_STREAMING or has no end time)
?job state is RUNNING (active streaming pipeline)
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | <principal> |
| target | <Dataflow streaming job (BatchJob)> |
| permissions | dataflow.jobs.updateContents |
| conditions | service_state |
| state logic | ACTIVE if dataflow.jobs.updateContents is confirmed in the effective grant at project scope AND the target job is a RUNNING streaming job. The code update takes effect without restart — there is no trigger_exists gate; the streaming pipeline is already running. CONDITIONAL(service_state) if job state is not confirmed as RUNNING (the streaming pipeline must already be running for this to be an active code-injection path). Do not emit for batch jobs (they do not support in-flight code updates via this permission). BLOCKED if a deny policy applies to dataflow.jobs.updateContents. |
Narrative
{principal.name} can update the user code of running streaming pipeline {job.name} (dataflow.jobs.updateContents), injecting attacker-controlled transforms that execute immediately as worker service account {sa.name} without restarting the job.
Raw rule rules/derived/gcp/dataflow.yaml
id: gcp-dataflow-update-contents-modify-code
emits: CanModifyCode
description: dataflow.jobs.updateContents on a streaming Dataflow job lets the principal inject attacker-controlled
transforms into the live pipeline; updated code runs immediately as the worker SA.
match_effective_permission:
action: dataflow.jobs.updateContents
resource_type: google.dataflow.Job
where:
- ?job is a STREAMING job (job type == JOB_TYPE_STREAMING or has no end time)
- ?job state is RUNNING (active streaming pipeline)
emit:
source_type: Identity
target_type: Compute
source: <principal>
target: <Dataflow streaming job (BatchJob)>
permissions:
- dataflow.jobs.updateContents
conditions:
- service_state
state_logic: "ACTIVE if dataflow.jobs.updateContents is confirmed in the effective grant at project\
\ scope AND the target job is a RUNNING streaming job. The code update takes effect without restart\
\ \u2014 there is no trigger_exists gate; the streaming pipeline is already running. CONDITIONAL(service_state)\
\ if job state is not confirmed as RUNNING (the streaming pipeline must already be running for this\
\ to be an active code-injection path). Do not emit for batch jobs (they do not support in-flight\
\ code updates via this permission). BLOCKED if a deny policy applies to dataflow.jobs.updateContents."
confidence: 0.93
derived_from:
- <dataflow.jobs.updateContents effective permission>
false_positive_note: "STREAMING ONLY: dataflow.jobs.updateContents is meaningful for code injection\
\ only on streaming jobs that are currently RUNNING. For batch jobs, this path does not apply \u2014\
\ emit as POTENTIAL or omit. Confirm job type (JOB_TYPE_STREAMING) and state (RUNNING) from the collected\
\ job resource before emitting ACTIVE. The downstream CanExecuteAs is derived by can-execute-as execute-as-via-code-modify\
\ off the CanModifyCode + ExecutesAs combination \u2014 cite, do not re-derive."
narrative: '{principal.name} can update the user code of running streaming pipeline {job.name} (dataflow.jobs.updateContents),
injecting attacker-controlled transforms that execute immediately as worker service account {sa.name}
without restarting the job.'