Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
HIGH severity

GHSA-ffq3-xpv3-j92q

HIGHFix: lepture/mistune@2b04d7b

GHSA-ffq3-xpv3-j92q is a high-severity (CVSS 7.5) CWE-407 vulnerability in mistune. O3 Security confirms whether GHSA-ffq3-xpv3-j92q is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Mistune block_parser: quadratic-time parsing on long lists of repeated reference-link definitions

Also known asCVE-2026-59928PYSEC-2026-2216
Published
Jul 20, 2026
Updated
Jul 20, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Jul 20, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐍mistune

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Type: Algorithmic-complexity DoS in reference-link definition handling. A markdown document with N reference-link definitions of the same key (or many distinct keys) takes O(N²) parser time. 5000 repeated [a]: u\n definitions take ~1.1 second; 10000 → ~4.5 seconds. File: src/mistune/block_parser.py (reference-link def parsing) and the surrounding ref_links env-dictionary handling. Root cause: every reference definition is parsed by scanning forward from each candidate position. The unikey normalisation runs per-def, the dictionary insert is per-def, and the lookup-by-label-then-iterate-defs path is linear in the number of stored defs. For input with N defs, the total work is O(N²).

Affected Code

src/mistune/block_parser.py — reference-definition rule fires on every line that matches [label]: url. For each one:

  • unikey(label) is called (linear scan of the label).
  • The def is appended to state.env['ref_links'].
  • Later inline-link resolution looks up by unikey(label) in the dict (O(1)) but the surrounding parser revisits the def list for paragraph-vs-def disambiguation.

The cumulative parse time grows as the square of the number of defs.

Why it's wrong: the parser does not amortise the def-list scan. A single forward pass with a hash-keyed dict (already in place) plus a per-line classifier should make this O(N).

Exploit Chain

  1. Application uses mistune to render attacker-supplied markdown. No plugins required.
  2. Attacker submits a 35 KB document of [a]: u\n repeated 5000 times followed by [click][a].
  3. CPU pegs for ~1.1 seconds. 10000 defs → ~4.5 s. 20000 → ~18 s. Doubling input quadruples time.

Security Impact

Attacker capability: small input → large CPU. Predictable scaling. Can be repeated. Preconditions: application uses mistune.create_markdown() (default config) on attacker-supplied markdown. Worth noting: the ref_links dictionary persists for the lifetime of the parse, so a long document with many defs builds up memory; with N defs of attacker-chosen length, the per-def normalisation cost compounds. Differential: PoC-verified against [email protected], default config:

import mistune, time
md = mistune.create_markdown()
for n in [1000, 2000, 5000, 10000]:
    s = '[a]: u\n' * n + '[click][a]'
    t = time.time()
    md(s)
    print(f'  ref defs * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms')

# Output (Python 3.13, Linux, 2.5GHz CPU):
#   ref defs *  1000  ( 7012b):    46ms
#   ref defs *  2000 (14012b):   186ms
#   ref defs *  5000 (35012b):  1121ms
#   ref defs * 10000 (70012b):  4400ms

The patched build (with the surrounding parser amortised to O(N)) keeps the time linear.

Suggested Fix

Replace the per-def re-scan with a single forward pass that classifies each line into ref_def | paragraph | other once and only inserts into ref_links once per def. The dict already exists; the wasted work is in the surrounding scan loop, not in the dict operations.

A regression test asserting that md('[a]: u\n' * 50_000 + '[click][a]') completes in under 1 second would catch any regression.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPImistuneall versions3.3.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for mistune. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update mistune to 3.3.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-ffq3-xpv3-j92q is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-ffq3-xpv3-j92q is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-ffq3-xpv3-j92q. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary **Type:** Algorithmic-complexity DoS in reference-link definition handling. A markdown document with N reference-link definitions of the same key (or many distinct keys) takes O(N²) parser time. 5000 repeated `[a]: u\n` definitions take ~1.1 second; 10000 → ~4.5 seconds. **File:** `src/mistune/block_parser.py` (reference-link def parsing) and the surrounding `ref_links` env-dictionary handling. **Root cause:** every reference definition is parsed by scanning forward from each candidate position. The `unikey` normalisation runs per-def, the dictionary insert is per-def, and the look
O3 Security · Impact-Aware SCA

Is GHSA-ffq3-xpv3-j92q in your dependencies?

O3 detects GHSA-ffq3-xpv3-j92q across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.