{"id":"PYSEC-2026-3863","summary":"Mistune: Denial of Service — RecursionError via Excessive Emphasis Markers in Markdown","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's _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['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 \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's 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 = '*' * 1000\ntry:\n    result = html(payload)\n    print('No crash - recursion handled')\nexcept RecursionError as e:\n    print(f'[VULN] RecursionError: {e} - Process would crash!')\nexcept Exception as e:\n    print(f'Error: {type(e).__name__}: {e}')\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) — 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'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.","aliases":["CVE-2026-76098","GHSA-6m44-fpc8-c3rq"],"modified":"2026-09-10T12:15:09.113441250Z","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"}],"affected":[{"package":{"name":"mistune","ecosystem":"PyPI","purl":"pkg:pypi/mistune"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.3.0"},{"fixed":"3.3.3"}]}],"versions":["3.3.0","3.3.1","3.3.2"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/mistune/PYSEC-2026-3863.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}]}