gcp-dataproc-init-action-inject-code
dataproc.clusters.update on initializationActions (or the GCS path they reference) injects code that executes as the cluster SA on the next node join or cluster restart.
match (effective permission)
{
"action": "dataproc.clusters.update",
"resource_type": "google.dataproc.Cluster"
}
where
the update targets config.initializationActions (shell scripts executed on node start)
OR the principal can write to the GCS path referenced by an init action (storage.objects.create on the init-script GCS path)
emit
| source type | Identity |
|---|---|
| target type | AnalyticsService |
| source | <principal> |
| target | <Dataproc cluster> |
| permissions | dataproc.clusters.update |
| conditions | iam_permission service_state |
| state logic | CONDITIONAL(service_state): the injected init script runs only on the NEXT node join (scale-out) or full cluster restart — not on currently running nodes. Upgrade to ACTIVE if the attacker can also trigger a cluster restart (dataproc.clusters.start after stop) or if the cluster auto-scales (which adds nodes, running init scripts). BLOCKED if an IAM deny prevents clusters.update. Also CONDITIONAL when the attack vector is a GCS-write to the init-script path (requires CanWriteData on the GCS bucket separately). |
Narrative
{principal.name} can inject code into {cluster.name}'s initialization actions (dataproc.clusters.update on initializationActions); the injected script will run as {sa.name} on the next node join or cluster restart.
Raw rule rules/derived/gcp/dataproc.yaml
id: gcp-dataproc-init-action-inject-code
emits: CanModifyCode
description: dataproc.clusters.update on initializationActions (or the GCS path they reference) injects
code that executes as the cluster SA on the next node join or cluster restart.
match_effective_permission:
action: dataproc.clusters.update
resource_type: google.dataproc.Cluster
where:
- the update targets config.initializationActions (shell scripts executed on node start)
- OR the principal can write to the GCS path referenced by an init action (storage.objects.create on the
init-script GCS path)
emit:
source_type: Identity
target_type: AnalyticsService
source: <principal>
target: <Dataproc cluster>
permissions:
- dataproc.clusters.update
conditions:
- iam_permission
- service_state
state_logic: "CONDITIONAL(service_state): the injected init script runs only on the NEXT node join (scale-out)\
\ or full cluster restart \u2014 not on currently running nodes. Upgrade to ACTIVE if the attacker\
\ can also trigger a cluster restart (dataproc.clusters.start after stop) or if the cluster auto-scales\
\ (which adds nodes, running init scripts). BLOCKED if an IAM deny prevents clusters.update. Also\
\ CONDITIONAL when the attack vector is a GCS-write to the init-script path (requires CanWriteData\
\ on the GCS bucket separately)."
false_positive_note: This is a DEFERRED code execution path, not immediate RCE like job submission.
The injected init script only runs when a node starts (new node added, or cluster restarted). Do NOT
emit ACTIVE unless a node-join or restart is confirmed to follow. For a static, fully provisioned
cluster with no auto-scaling, this may never trigger until an operator action. CITE can-execute-as
execute-as-via-code- modify + cicd-chains cicd-modify-buildspec-executes-as for the CanExecuteAs collapse
once the code runs.
narrative: '{principal.name} can inject code into {cluster.name}''s initialization actions (dataproc.clusters.update
on initializationActions); the injected script will run as {sa.name} on the next node join or cluster
restart.'