VAITP Dataset

← Back to the dataset

CVE-2026-59929

Mistune's safe_url filter can be bypassed with unsafe schemes, causing XSS.

  • CVSS 6.1
  • CWE-79
  • Input Validation and Sanitization
  • Remote

Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.3.0, the safe_url filter in src/mistune/renderers/html.py blocks only javascript:, vbscript:, file:, and data: schemes, allowing legacy or chained schemes such as feed:, view-source:, jar:, livescript:, mocha:, ms-its:, mk:, and res: to reach rendered href and src attributes and potentially execute script in affected user agents. This issue is fixed in version 3.3.0.

CVSS base score
6.1
Published
2026-07-08
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
Arbitrary Code Execution
Affected component
Mistune
Fixed by upgrading
Yes

Solution

Upgrade to Mistune version 3.3.0 or later.

Vulnerable code sample

import mistune

# In vulnerable versions (e.g., mistune < 2.0.3), the safe_url filter
# only blocked a few specific URL schemes. Schemes like 'livescript:' were not blocked.
# This example uses a markdown string with such a scheme.

malicious_markdown_input = "[This is a link](livescript:alert('xss'))"

# The mistune.html() function processes the markdown.
# In a vulnerable version, it would fail to sanitize the dangerous URL.
rendered_html_output = mistune.html(malicious_markdown_input)

# The resulting HTML contains the unfiltered, malicious href attribute,
# demonstrating the vulnerability.
# Expected output with a vulnerable version:
# <p><a href="livescript:alert('xss')">This is a link</a></p>
print(rendered_html_output)

Patched code sample

import re

# This code represents the fixed approach, using an allowlist for URL schemes.
# The vulnerability existed because the old code used a blocklist that missed
# several dangerous schemes. The fix was to switch to an allowlist, only
# permitting known safe schemes.

_SAFE_URL_SCHEMES = {
    'http',
    'https',
    'ftp',
    'mailto',
    'tel',
}


def safe_url(url: str) -> str:
    """
    Sanitize a URL by checking its scheme against a predefined allowlist.

    This function represents the logic of the patched code. It parses the
    scheme from the URL and checks if it is present in the `_SAFE_URL_SCHEMES`
    set. If the scheme is not present or if the URL has no scheme (e.g., a
    relative path), it is considered safe. Otherwise, the URL is replaced with
    a sanitized string.
    """
    try:
        scheme, _, _ = url.partition(':')
        # A scheme must contain only alphabetic characters.
        # This is a bit stricter than the spec, but safe.
        if not scheme or (scheme.isalpha() and scheme.lower() in _SAFE_URL_SCHEMES):
            return url
    except ValueError:
        # Fallback for potential parsing errors, though partition is safe.
        pass

    return '#sanitized'

# --- Demonstration of the fix ---

# Vulnerable schemes are now blocked
vulnerable_urls = [
    "javascript:alert('XSS')",
    "vbscript:msgbox('XSS')",
    "data:text/html,<script>alert('XSS')</script>",
    "feed:javascript:alert(1)",
    "view-source:javascript:alert(1)",
    "livescript:alert(1)",
]

# Safe schemes are still allowed
safe_urls = [
    "https://example.com",
    "http://example.com/foo",
    "ftp://user@example.com",
    "mailto:user@example.com",
    "tel:123-456-7890",
    "/relative/path.html",
    "#fragment",
]

print("--- Testing vulnerable URLs (should be sanitized) ---")
for url in vulnerable_urls:
    print(f'"{url}" -> "{safe_url(url)}"')

print("\n--- Testing safe URLs (should pass through) ---")
for url in safe_urls:
    print(f'"{url}" -> "{safe_url(url)}"')

Payload

[XSS](livescript:alert(1))

Cite this entry

@misc{vaitp:cve202659929,
  title        = {{Mistune's safe_url filter can be bypassed with unsafe schemes, causing XSS.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-59929},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-59929/}}
}
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 ::