GHSA-6M44-FPC8-C3RQ

Vulnerability from github – Published: 2026-09-02 15:14 – Updated: 2026-09-02 15:14
VLAI
Summary
Mistune: Denial of Service — RecursionError via Excessive Emphasis Markers in Markdown
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.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mistune"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.3.0"
            },
            {
              "fixed": "3.3.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-76098"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-02T15:14:55Z",
    "nvd_published_at": "2026-08-24T20:17:19Z",
    "severity": "HIGH"
  },
  "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": "GHSA-6m44-fpc8-c3rq",
  "modified": "2026-09-02T15:14:55Z",
  "published": "2026-09-02T15:14:55Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…