CVE-2026-59922
Mistune formatting plugins are vulnerable to DoS via CPU exhaustion.
- CVSS 7.5
- CWE-407
- Resource Management
- Remote
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.3.0, a run of closed tilde, equals-sign, or caret marker pairs around a character causes quadratic work in src/mistune/plugins/formatting.py when the strikethrough, mark, or insert plugin scans for matching markers from each possible start position, allowing denial of service through CPU exhaustion. This issue is fixed in version 3.3.0.
- CWE
- CWE-407
- CVSS base score
- 7.5
- Published
- 2026-07-08
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Algorithm
- Code defect classification
- Incorrect Algorithm
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- Mistune
- Fixed by upgrading
- Yes
Solution
Upgrade Mistune to version 3.3.0 or later.
Vulnerable code sample
def vulnerable_parser(text, marker="~~"):
"""
This function is a simplified representation of the vulnerable logic.
For each potential starting marker, it scans the rest of the string
from that point to find a closing marker, leading to quadratic complexity.
"""
i = 0
marker_len = len(marker)
text_len = len(text)
# Outer loop iterates through the text
while i < text_len:
# Check if the current position is a starting marker
if text[i:i + marker_len] == marker:
# If a starting marker is found, start a new scan for the closing marker
# This nested search causes the O(n^2) behavior on crafted input
j = i + marker_len
found_end = False
while j < text_len:
if text[j:j + marker_len] == marker:
# Closing marker found. In a real parser, content would be processed.
# We advance the main index 'i' past this entire section.
i = j + marker_len
found_end = True
break
j += 1
# If no closing marker was found, treat the start as literal text
if not found_end:
i += marker_len
else:
# Not a marker, advance by one character
i += 1
# Craft a payload that triggers the quadratic behavior.
# A long string of opening markers will cause many nested scans.
# For example, with "~~" * 10, the first "~~" will cause a scan over the next 9 pairs.
# The second "~~" will cause a scan over the next 8 pairs, and so on.
# A value of 20000 or higher will cause significant CPU load for many seconds.
payload = "~~" * 20000 + "vulnerable text"
# This call will hang for a long time due to CPU exhaustion,
# demonstrating the denial-of-service vulnerability.
vulnerable_parser(payload)Patched code sample
def _fixed_parse_formatting(text, token_type, marker):
"""
This function demonstrates the logic used to fix the denial-of-service
vulnerability described in CVE-2026-59922 for the Mistune library.
The vulnerability was caused by a quadratic-time (O(n^2)) complexity issue.
The old implementation could repeatedly re-scan the string when processing
inputs with many formatting markers (e.g., '~~a~~b~~c~~...'), leading to
CPU exhaustion.
This fixed implementation avoids that vulnerability by performing a single,
linear-time (O(n)) scan of the string. It uses `text.find()` to locate
markers and manually advances a position index (`pos`), ensuring that each
part of the string is processed only once.
"""
tokens = []
pos = 0
marker_len = len(marker)
while pos < len(text):
# Find the next opening marker from the current position. `text.find()`
# is highly optimized and is a key part of the linear scan.
start_pos = text.find(marker, pos)
if start_pos == -1:
# No more markers found; add the remainder of the text and exit.
if pos < len(text):
tokens.append(('text', text[pos:]))
break
# Capture any plain text located before this marker.
if start_pos > pos:
tokens.append(('text', text[pos:start_pos]))
# Find the corresponding closing marker. The search starts immediately
# after the opening marker's position.
end_pos = text.find(marker, start_pos + marker_len)
if end_pos == -1:
# No closing marker was found, so the opening marker and the rest
# of the string are treated as plain text.
tokens.append(('text', text[start_pos:]))
break
# Extract the content between the markers.
content = text[start_pos + marker_len : end_pos]
# Check if content is valid (not empty or just whitespace).
if content and not content.isspace():
tokens.append((token_type, content))
# The crucial step: advance the main position (`pos`) to the end of
# the just-processed token. This prevents re-scanning this segment.
pos = end_pos + marker_len
else:
# If content is invalid, treat the opening marker as plain text
# and continue scanning from the character right after it.
tokens.append(('text', text[start_pos : start_pos + marker_len]))
pos = start_pos + marker_len
return tokensPayload
'~~' * 20000 + 'a' + '~~'
Cite this entry
@misc{vaitp:cve202659922,
title = {{Mistune formatting plugins are vulnerable to DoS via CPU exhaustion.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-59922},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-59922/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
