2.9 KiB
Prefect — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Two defects in Prefect's cache policy and deployment step runner. One uses a list for cache key exclusion with O(N×M) overhead per cached task; the other uses a list for warning deduplication causing O(W²) overhead. Patches ready for upstream review.
The Defects
pre-0001 (PATCHED — HIGH): src/prefect/cache_policies.py:364,380-381
# Inputs.exclude: list[str]
# Inside compute_key() — per cached task invocation:
for key in inputs:
if key not in exclude: # O(N×M) — inputs × exclude list
filtered[key] = inputs[key]
if key not in exclude where exclude is a list[str]. O(M) scan per input key per task invocation. For N inputs and M exclusions: O(N × M) per cached task invocation. Measured ratio: 100×.
pre-0002 (PATCHED — LOW): src/prefect/deployments/steps/core.py:191-202
# Inside warning dedup loop:
printed_messages = []
for warning in w:
if str(warning.message) not in printed_messages: # O(W) list scan per warning
printed_messages.append(str(warning.message))
# O(W²) warning dedup
if str(warning.message) not in printed_messages where printed_messages is a list. O(W²) dedup. Low severity.
Complexity Proof
pre-0001: For N=100 inputs, M=100 excluded keys:
- Per invocation: O(N×M) = 10,000 comparisons
- Fixed:
frozenset(exclude)atcompute_key()entry → O(N + M) - 100× measured ratio.
pre-0002: Low severity — warning dedup is cold path.
Impact
pre-0001 affects all Prefect users using cache_policy=Inputs(exclude=[...]) — a standard pattern for caching task results while excluding timestamps or volatile fields. Every cached task invocation hits O(N×M). Prefect is a widely used Python workflow orchestration framework. Data pipelines with many cached tasks and long exclusion lists maximize the defect.
The Fix
pre-0001: Convert exclude to frozenset at compute_key() entry:
# Before
for key in inputs:
if key not in exclude: # O(M) list scan per key
filtered[key] = inputs[key]
# After
# CWE-407 fix: frozenset for O(1) membership instead of O(M) list scan.
exclude_set = frozenset(exclude)
for key in inputs:
if key not in exclude_set: # O(1) frozenset lookup
filtered[key] = inputs[key]
pre-0002: Replace printed_messages list with set.
Patch
defects/prefect/patch/pre-0001-0002-cache-policy-frozenset.patch
What We Ask
- Confirm receipt and assign a GitHub Security Advisory or issue reference.
- Validate the patch against your cache policy and deployment steps test suite.
- Assess CVE eligibility — pre-0001 fires on every cached task invocation with exclusions.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
Contact: see cover email. This brief is confidential until coordinated disclosure.