PYSEC-2026-3863

Vulnerability from pysec - Published: 2026-09-10 09:44 - Updated: 2026-09-10 11:02
VLAI
Details

Summary

Mistune v3.3.2 is vulnerable to a Denial of Service (DoS) attack via uncontrolled recursion in the HTML rendering of deeply-nested emphasis tokens. By submitting Markdown containing approximately 1,000 consecutive asterisk characters, an attacker causes the Python process to crash with RecursionError.

Details

The InlineParser's _process_emphasis_delimiters() creates deeply nested tokens from consecutive emphasis markers (every 2 asterisks add one nesting level). With 1,000 consecutive asterisks, approximately 500 levels of nesting are produced. The HTMLRenderer.render_token() method (src/mistune/renderers/html.py:40-57) renders these tokens recursively: when a token has children, line 48 calls self.render_tokens(token['children'], state), entering child rendering. Each nesting level produces ~2 stack frames, so 500 levels ≈ 1,000 frames, exceeding Python's default recursion limit (sys.getrecursionlimit() = 1000). The emphasis() and strong() methods (lines 80-84) wrap recursively rendered child content in and tags, perpetuating the recursion. This vulnerability affects all mistune APIs including markdown() and html().

Core vulnerable code path:

# src/mistune/renderers/html.py:40-57
def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
    func = self._get_method(token["type"])
    attrs = token.get("attrs")
    if "raw" in token:
        text = token["raw"]
    elif "children" in token:
        text = self.render_tokens(token["children"], state)
    else:
        if attrs:
            return func(**attrs)
        else:
            return func()
    if attrs:
        return func(text, **attrs)
    else:
        return func(text)

The recursive call to render_tokens() on line 48 processes nested child tokens. With 500 levels of nested emphasis/strong tokens, this recursion exceeds Python's default recursion limit of 1000, causing a RecursionError.

# src/mistune/renderers/html.py:80-84
def emphasis(self, text: str) -> str:
    return "<em>" + text + "</em>"

def strong(self, text: str) -> str:
    return "<strong>" + text + "</strong>"

The emphasis() and strong() methods wrap their text content (which is itself the recursively-rendered output of nested child tokens) in HTML tags, creating the chain of recursion: each call to strong() includes rendered children that themselves call strong(), etc.

POC

from mistune import html

payload = '*' * 1000
try:
    result = html(payload)
    print('No crash - recursion handled')
except RecursionError as e:
    print(f'[VULN] RecursionError: {e} - Process would crash!')
except Exception as e:
    print(f'Error: {type(e).__name__}: {e}')

1

Impact

CVSS 3.1: 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. An attacker can crash any server process using mistune with approximately 2KB of Markdown input. In web applications, this can be triggered through user-generated content such as forum posts or comments, causing denial of service for all concurrent users sharing the same Python process. Both mistune.markdown() and mistune.html() are affected.

Remediation

  1. Add a maximum nesting depth limit in the emphasis parsing stage, similar to BlockParser's max_nested_level mechanism (currently at DEFAULT_MAX_NESTED_LEVEL = 20). When the limit is exceeded, treat excess emphasis markers as literal text. 2. Alternatively, refactor HTMLRenderer to use an explicit stack-based iterative approach instead of recursive calls for rendering nested tokens. 3. As a defense-in-depth measure, document the recursion risk and recommend that applications deploying mistune set a higher recursion limit or implement request-level timeouts.
Impacted products
Name purl
mistune pkg:pypi/mistune

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mistune",
        "purl": "pkg:pypi/mistune"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.3.0"
            },
            {
              "fixed": "3.3.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "3.3.0",
        "3.3.1",
        "3.3.2"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-76098",
    "GHSA-6m44-fpc8-c3rq"
  ],
  "details": "## Summary\nMistune v3.3.2 is vulnerable to a Denial of Service (DoS) attack via uncontrolled recursion in the HTML rendering of deeply-nested emphasis tokens. By submitting Markdown containing approximately 1,000 consecutive asterisk characters, an attacker causes the Python process to crash with RecursionError.\n\n## Details\nThe InlineParser\u0027s _process_emphasis_delimiters() creates deeply nested \u003cstrong\u003e tokens from consecutive emphasis markers (every 2 asterisks add one nesting level). With 1,000 consecutive asterisks, approximately 500 levels of nesting are produced. The HTMLRenderer.render_token() method (src/mistune/renderers/html.py:40-57) renders these tokens recursively: when a token has children, line 48 calls self.render_tokens(token[\u0027children\u0027], state), entering child rendering. Each nesting level produces ~2 stack frames, so 500 levels \u2248 1,000 frames, exceeding Python\u0027s default recursion limit (sys.getrecursionlimit() = 1000). The emphasis() and strong() methods (lines 80-84) wrap recursively rendered child content in \u003cem\u003e and \u003cstrong\u003e tags, perpetuating the recursion. This vulnerability affects all mistune APIs including markdown() and html().\n\nCore vulnerable code path:\n\n```python\n# src/mistune/renderers/html.py:40-57\ndef render_token(self, token: Dict[str, Any], state: BlockState) -\u003e str:\n    func = self._get_method(token[\"type\"])\n    attrs = token.get(\"attrs\")\n    if \"raw\" in token:\n        text = token[\"raw\"]\n    elif \"children\" in token:\n        text = self.render_tokens(token[\"children\"], state)\n    else:\n        if attrs:\n            return func(**attrs)\n        else:\n            return func()\n    if attrs:\n        return func(text, **attrs)\n    else:\n        return func(text)\n```\n\nThe recursive call to render_tokens() on line 48 processes nested child tokens. With 500 levels of nested emphasis/strong tokens, this recursion exceeds Python\u0027s default recursion limit of 1000, causing a RecursionError.\n\n```python\n# src/mistune/renderers/html.py:80-84\ndef emphasis(self, text: str) -\u003e str:\n    return \"\u003cem\u003e\" + text + \"\u003c/em\u003e\"\n\ndef strong(self, text: str) -\u003e str:\n    return \"\u003cstrong\u003e\" + text + \"\u003c/strong\u003e\"\n```\n\nThe emphasis() and strong() methods wrap their text content (which is itself the recursively-rendered output of nested child tokens) in HTML tags, creating the chain of recursion: each call to strong() includes rendered children that themselves call strong(), etc.\n\n## POC\n\n``` wiki\nfrom mistune import html\n\npayload = \u0027*\u0027 * 1000\ntry:\n    result = html(payload)\n    print(\u0027No crash - recursion handled\u0027)\nexcept RecursionError as e:\n    print(f\u0027[VULN] RecursionError: {e} - Process would crash!\u0027)\nexcept Exception as e:\n    print(f\u0027Error: {type(e).__name__}: {e}\u0027)\n```\n\n\u003cimg width=\"1139\" height=\"664\" alt=\"1\" src=\"https://github.com/user-attachments/assets/d2095b9f-0a6a-4bef-8013-cbc6946cf8f1\" /\u003e\n\n\n\n## Impact\nCVSS 3.1: 7.5 (High) \u2014 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. An attacker can crash any server process using mistune with approximately 2KB of Markdown input. In web applications, this can be triggered through user-generated content such as forum posts or comments, causing denial of service for all concurrent users sharing the same Python process. Both mistune.markdown() and mistune.html() are affected.\n\n## Remediation\n1. Add a maximum nesting depth limit in the emphasis parsing stage, similar to BlockParser\u0027s max_nested_level mechanism (currently at DEFAULT_MAX_NESTED_LEVEL = 20). When the limit is exceeded, treat excess emphasis markers as literal text. 2. Alternatively, refactor HTMLRenderer to use an explicit stack-based iterative approach instead of recursive calls for rendering nested tokens. 3. As a defense-in-depth measure, document the recursion risk and recommend that applications deploying mistune set a higher recursion limit or implement request-level timeouts.",
  "id": "PYSEC-2026-3863",
  "modified": "2026-09-10T11:02:14.278433Z",
  "published": "2026-09-10T09:44:59.543987Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/security/advisories/GHSA-6m44-fpc8-c3rq"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-76098"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/commit/0938fb781d0aded99de801b340ec1f8debeae5b2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/lepture/mistune"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/releases/tag/v3.3.3"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/mistune"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-6m44-fpc8-c3rq"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Mistune: Denial of Service \u2014 RecursionError via Excessive Emphasis Markers in Markdown"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…