CVE-2026-44899
Mistune Image directive allows CSS injection via width/height options.
- CVSS 6.1
- CWE-79
- Input Validation and Sanitization
- Remote
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:\.\d*)?"). When the validated value is not a plain integer, render_block_image() inserts it directly into a style="width:…;" or style="height:…;" attribute. Because the value was accepted by the prefix-only regex, any CSS after the leading digits reaches the style= attribute verbatim and without escaping. This vulnerability is fixed in 3.2.1.
- CWE
- CWE-79
- CVSS base score
- 6.1
- Published
- 2026-05-26
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Input Validation and Sanitization
- Subcategory
- Cross-Site Scripting (XSS)
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- Mistune
- Fixed by upgrading
- Yes
Solution
Upgrade Mistune to version 3.2.1 or later.
Vulnerable code sample
import re
import html
# Represents the vulnerable regex from Mistune < 3.2.1
# It incorrectly validates by only checking if the string STARTS with a number.
_num_re = re.compile(r"^\d+(?:\.\d*)?")
def vulnerable_render_image(src, alt="", width=None, height=None):
"""
This function is a minimal representation of the vulnerable logic in
mistune.directives.image.Image.render_block_image before it was patched.
"""
attrs = {'src': html.escape(src), 'alt': html.escape(alt)}
style = []
# Vulnerable check for the 'width' parameter
if width and _num_re.match(width):
# The flaw: The un-sanitized 'width' string is concatenated directly
# into the style attribute because the regex only checked the prefix.
style.append('width: ' + width + ';')
# Vulnerable check for the 'height' parameter
if height and _num_re.match(height):
style.append('height: ' + height + ';')
if style:
# The concatenated style string is added to the attributes.
attrs['style'] = ' '.join(style)
# Construct the final HTML tag string
attr_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
return f'<img {attr_str}>'
# --- Demonstration of the Vulnerability ---
# This malicious payload starts with "42", which passes the flawed regex.
# It then injects CSS to close the style attribute and add a new
# 'onload' attribute to execute JavaScript, achieving an XSS attack.
malicious_payload = "42px;\" onload=\"alert('XSS via CVE-2026-44899')\" foo=\""
# Call the vulnerable function with the malicious payload.
generated_html = vulnerable_render_image("image.png", "test", width=malicious_payload)
# The output shows the injected 'onload' attribute in the generated HTML.
print(generated_html)
# Expected output:
# <img src="image.png" alt="test" style="width: 42px;" onload="alert('XSS via CVE-2026-44899')" foo=";">Patched code sample
import re
import html
# This code demonstrates the fix for the vulnerability (correctly identified as
# CVE-2023-44899). The vulnerability was caused by a regex that only checked
# if a string *started* with a number, not if the *entire string* was a number.
# The fix involves changing the regex to anchor it to the end of the string
# with '$'. This ensures the entire value is validated, preventing the injection
# of arbitrary CSS.
_FIXED_NUM_RE = re.compile(r"^\d+(?:\.\d*)?$")
def render_image_with_fixed_validation(alt: str, src: str, width: str = None) -> str:
"""
Simulates rendering an image tag using the patched validation logic from
mistune version 3.2.1. It will only apply the 'width' if it strictly
matches the numerical pattern, thus preventing CSS injection.
"""
style_str = ""
# The 'if' condition below represents the core of the security fix.
# It uses the corrected regex to validate the entire 'width' string.
if width and _FIXED_NUM_RE.match(width):
# The value is now confirmed to be a simple number, so it is safe.
# The actual patch also correctly appends 'px' to form valid CSS.
style_str = f'style="width: {width}px;"'
safe_alt = html.escape(alt, quote=True)
safe_src = html.escape(src, quote=True)
# Construct the final tag, only including the style attribute if it was generated.
return f'<img src="{safe_src}" alt="{safe_alt}"{f" {style_str}" if style_str else ""}>'
if __name__ == '__main__':
# --- DEMONSTRATION ---
# 1. A valid, non-malicious width value.
valid_width = "150"
print("--- Using a valid width ---")
print(f"Input width: '{valid_width}'")
print("Resulting HTML (Correct):")
print(render_image_with_fixed_validation(
"A test image", "image.jpg", valid_width
))
print("-" * 40)
# 2. A malicious string that the vulnerable regex would have accepted.
# The old regex would match "100" and let the rest of the string pass.
malicious_width = "100; background: red; /*"
print("--- Using a malicious width ---")
print(f"Input width: '{malicious_width}'")
print("Resulting HTML (Securely Handled):")
# The fixed code rejects this input, and no style attribute is added.
print(render_image_with_fixed_validation(
"An evil image", "evil.jpg", malicious_width
))Payload
.. image:: /image.png
:width: 1px; background-image: url(https://attacker.example.com/xss);
Cite this entry
@misc{vaitp:cve202644899,
title = {{Mistune Image directive allows CSS injection via width/height options.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-44899},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-44899/}}
}
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 ::
