java-topology/whitepaper/outreach/prefect.md

2.9 KiB
Raw Blame History

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) at compute_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

  1. Confirm receipt and assign a GitHub Security Advisory or issue reference.
  2. Validate the patch against your cache policy and deployment steps test suite.
  3. Assess CVE eligibility — pre-0001 fires on every cached task invocation with exclusions.
  4. Coordinate a disclosure date — we are targeting 90 days from first contact.

Contact: see cover email. This brief is confidential until coordinated disclosure.