CVE-2026-59922: Mistune plugins/formatting Quadratic-time Parsing On Long Runs Vulnerability
HIGHCVE-2026-59922 is a HIGH (CVSS 7.5) quadratic-time parsing on long runs vulnerability (CWE-1333: Inefficient Regular Expression Complexity) affecting Mistune plugins/formatting libraries in the PyPI ecosystem. The issue involves quadratic-time parsing on long runs of `~~x~~`, `==x==`, and `^^x^^` markers (strikethrough / mark / insert). Remediation is available by upgrading affected packages to version 3.3.0.
CVE-2026-59922 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-59922
To remediate this vulnerability, upgrade all affected Mistune plugins/formatting dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install mistune>=3.3.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show mistune
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-59922, inspect your installed packages using pip:
pip show mistune
Analyze the terminal output to locate any version of Mistune plugins/formatting in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.3.0), your application is affected.
Note: The pip show mistune command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-1333: Inefficient Regular Expression Complexity, where the product uses a regular expression with a worst-case computational complexity that is inefficient and possibly exponential. In Mistune plugins/formatting, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-59922 Affected Mistune plugins/formatting Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
mistune
|
PyPI |
< 3.3.0
|
3.3.0
|
References
Frequently Asked Questions
What is CVE-2026-59922?
Which Mistune plugins/formatting versions are affected?
What version fixes CVE-2026-59922?
Is mistune affected?
What is the CVSS score of CVE-2026-59922?
What CWE is associated with CVE-2026-59922?
How can I check PyPI dependencies for CVE-2026-59922?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-c8j7-8cv4-2xmq",
"modified" : "2026-07-20T21:34:37Z",
"published" : "2026-07-20T21:34:37Z",
"aliases" : [ "CVE-2026-59922" ],
"summary" : "Mistune plugins/formatting: quadratic-time parsing on long runs of `~~x~~`, `==x==`, and `^^x^^` markers (strikethrough / mark / insert)",
"details" : "## Summary\n\n**Type:** Algorithmic-complexity denial of service. A run of N closed pairs `~~x~~~~x~~...` (or the analogous `==x==` for `mark`, `^^x^^` for `insert`) causes O(N²) work in the formatting parser. With the `strikethrough`, `mark`, or `insert` plugin enabled, an 8 KB input pegs the CPU for ~4 seconds; 16 KB → ~17 seconds. \n**File:** `src/mistune/plugins/formatting.py`, lines 13-15 (the `_STRIKE_END` / `_MARK_END` / `_INSERT_END` patterns and their per-position scan).\n**Root cause:** for each opening `~~`/`==`/`^^` the parser scans forward for the matching close pattern. The scan itself uses a bounded regex, but the parser tries the close-scan at every potential start position. For input shaped like `~~x~~` repeated N times, every `~~` is examined as a possible start, each scan covers up to the end of input. Total work is O(N²). Default config without these plugins handles the same input in linear time (4 ms for 4000 reps), confirming the cost is in the formatting plugin's per-marker scan, not in core parsing.\n\n## Affected Code\n\n**File:** `src/mistune/plugins/formatting.py`, lines 12-16.\n\n```python\n_STRIKE_END = re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\~|[^\\s~])~~(?!~)\")\n_MARK_END = re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\=|[^\\s=])==(?!=)\")\n_INSERT_END = re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\\\^|[^\\s^])\\^\\^(?!\\^)\")\n# Each pattern is scanned forward from every start position fired by the\n# corresponding inline rule. The end-pattern itself is bounded; the cost\n# comes from the surrounding parser invoking the scan at every '~~' / '==' / '^^'\n# token in the input, giving N starts × O(N) per scan = O(N^2) total.\n```\n\n**Why it's wrong:** the same algorithmic-complexity flaw class as `[` / `[a` parsing in core: a per-token retry loop without memoisation of failed positions. Each formatting marker is tried as both a potential start and as a continuation. A linear-pass delimiter-stack algorithm (matching how `commonmark-py` and `markdown-it-py` handle emphasis) would do this work in O(N) total. The bounded regex on each individual scan does not bound the parser-level repetition.\n\n## Exploit Chain\n\n1. Application uses mistune to render user-supplied markdown and has any of the formatting plugins enabled (`plugins=['strikethrough']`, `['mark']`, `['insert']`, or any superset). These plugins are commonly enabled because GitHub-flavoured-Markdown compatibility requires `~~strikethrough~~` and many editors emit `==highlighting==` and `^^underline^^` shortcuts.\n2. Attacker submits an 8 KB markdown payload of the form `~~x~~~~x~~~~x~~...` (40 000 characters of `~~x~~` repeated 8000 times, or the analogous shape with `==` / `^^`).\n3. Server calls `mistune.create_markdown(plugins=['strikethrough'])(payload)`. CPU pegs for ~4 seconds; 16 KB → ~17 seconds; 32 KB → ~70 seconds. Pure CPU cost, no significant memory growth.\n4. Repeating the request floods the worker pool. On a single-thread WSGI handler this is one request per outage; on a thread pool, a small number of concurrent attackers exhausts capacity.\n\n## Security Impact\n\n**Severity:** sec-high. Network-reachable, no authentication, predictable scaling, single-payload primitive. Only requires a user-supplied markdown sink and a formatting plugin enabled — both are common.\n**Attacker capability:** small input → large CPU. Doubling input size quadruples CPU time. Sustained requests deny service to other users.\n**Preconditions:** application uses mistune with any of `strikethrough`, `mark`, or `insert` plugins enabled. Default config does NOT enable these (so the attack only fires against the substantial deployed population that turns them on for GFM/markdown-extra compatibility).\n**Differential:** PoC-verified against mistune@3.2.1:\n\n```python\nimport mistune, time\nmd = mistune.create_markdown(plugins=['strikethrough'])\nfor n in [500, 1000, 2000, 4000, 8000]:\n s = '~~x~~' * n\n t = time.time()\n md(s)\n print(f' ~~x~~ * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms')\n\n# Output (Python 3.13, Linux, 2.5GHz CPU):\n# ~~x~~ * 500 (2500b): 19ms\n# ~~x~~ * 1000 (5000b): 71ms\n# ~~x~~ * 2000 (10000b): 272ms\n# ~~x~~ * 4000 (20000b): 1090ms\n# ~~x~~ * 8000 (40000b): 4302ms\n\n# Identical scaling for `==x==` (mark) and `^^x^^` (insert):\nmd = mistune.create_markdown(plugins=['mark'])\nmd('==x==' * 4000) # ~1100ms\nmd = mistune.create_markdown(plugins=['insert'])\nmd('^^x^^' * 4000) # ~1080ms\n\n# Without the plugin, the same input parses in linear time:\nmd = mistune.create_markdown() # no plugins\nmd('~~x~~' * 4000) # 4ms (1000x faster)\n```\n\nThe patched build (with the suggested fix below — either a delimiter-stack rewrite or a hard cap on the number of unmatched markers tracked) keeps the time linear in N.\n\n## Suggested Fix\n\nThe minimal fix is to cap the number of simultaneously-tracked unmatched markers, treating extras as literal text. The proper fix is a single-pass delimiter-stack algorithm matching the CommonMark reference implementation. Surgical patch:\n\n```diff\n--- a/src/mistune/plugins/formatting.py\n+++ b/src/mistune/plugins/formatting.py\n@@ ... in the parse_strikethrough / parse_mark / parse_insert functions\n+ # Bound the number of open markers the parser will track concurrently.\n+ # Inputs with more than this many open ~~ / == / ^^ in flight are\n+ # almost certainly adversarial; CommonMark gives no semantics to\n+ # deeply nested unmatched markers.\n+ MAX_OPEN_MARKERS = 100\n+ if open_marker_count > MAX_OPEN_MARKERS:\n+ # treat remaining markers as literal text, do not invoke the\n+ # forward-scan to find a close\n+ ...\n```\n\nA regression test should assert that `md('~~x~~' * 50_000)` completes in under 1 second. The same fix shape applies to `_MARK_END` and `_INSERT_END`.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "mistune"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.3.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/lepture/mistune/security/advisories/GHSA-c8j7-8cv4-2xmq"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-59922"
}, {
"type" : "WEB",
"url" : "https://github.com/lepture/mistune/commit/96d0f57f8fe9eeb06bb4cff521962a27d7c402e7"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/lepture/mistune"
}, {
"type" : "WEB",
"url" : "https://github.com/lepture/mistune/releases/tag/v3.3.0"
}, {
"type" : "WEB",
"url" : "https://github.com/pypa/advisory-database/tree/main/vulns/mistune/PYSEC-2026-2210.yaml"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-1333", "CWE-407" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-20T21:34:37Z",
"nvd_published_at" : "2026-07-08T17:17:27Z"
}
}