# 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`

```python
# 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`

```python
# 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:

```python
# 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.
