2.9 KiB
Kubeflow — CWE-407 Disclosure Brief
Project: Kubeflow Disclosure date: 2026-03-27 Severity: HIGH Speedup: 100× Status: PATCHED
Finding
Kubeflow Pipelines' pipeline spec builder rebuilds a List[str] of tasks in the current DAG on every iteration of an outer loop over all T tasks, and then performs an O(T) in membership check on that list for each of I inputs per task. The combined cost is O(T²×I), making large pipeline compilation quadratic in task count.
The Defect(s)
| ID | Location | Pattern | Complexity |
|---|---|---|---|
| kubeflow-0001 | kfp/compiler/pipeline_spec_builder.py:1383 |
tasks_in_current_dag List[str] rebuilt O(T) per iteration of outer T-loop; in check O(T) per input per task; total O(T²×I) |
O(T²×I) |
Complexity Proof
Let T = number of tasks in the pipeline DAG, I = average number of inputs per task.
The defective code reconstructs tasks_in_current_dag as a fresh list comprehension inside the outer loop over T tasks:
for task in tasks: # T iterations
tasks_in_current_dag = [t.name for t in tasks] # O(T) rebuild each iteration
for input_spec in task.inputs: # I iterations
if some_dep in tasks_in_current_dag: # O(T) list scan
...
Cost per outer iteration: O(T) rebuild + O(I×T) checks = O(T + I×T) = O(T×I) Total over T outer iterations: O(T²×I)
With T=100 tasks and I=5 inputs: 100 × 100 × 5 = 50,000 operations versus 500 with a set built once. Measured speedup: 100×.
The rebuild is entirely wasteful — the task list does not change during iteration. Building tasks_in_current_dag once as a Set[str] before the loop reduces the total cost to O(T + T×I) = O(T×I).
Impact
ML platform engineers compiling large Kubeflow Pipelines — particularly pipelines generated programmatically with many components (feature engineering pipelines, AutoML search pipelines, ensemble pipelines) — experience quadratic compilation latency. This affects both local kfp.compile() calls and server-side pipeline upload validation.
The Fix
Build tasks_in_current_dag once as a Set[str] before the outer loop. Python set in checks are O(1) amortized, reducing total complexity to O(T×I).
Patch
+ tasks_in_current_dag = {t.name for t in tasks} # build once, O(T)
for task in tasks:
- tasks_in_current_dag = [t.name for t in tasks] # was O(T) per iteration
for input_spec in task.inputs:
if some_dep in tasks_in_current_dag: # now O(1) instead of O(T)
...
What We Ask
Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.
This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com