GHSA-8g87-j6q8-g93x: Mistune Math Plugin has an XSS Escape Bypass
SummaryThe mistune math plugin renders inline math (`$...$`) and block math (`$$...$$`) by concatenating the raw user-supplied content directly into the HTML output **without any HTML escaping**. This occurs even when the parser is explicitly created with `escape=True`, which is supposed to guarantee that all user-controlled text is sanitised before reaching the DOM.The result is a silent contract violation: a developer who enables `escape=True` reasonably expects complete XSS protection, but the math plugin operates as an independent render path that ignores the renderer's `_escape` flag entirely.Details**File:** `src/mistune/plugins/math.py`Both functions take `text` directly from the parsed token and concatenate it into the output string. Neither function:calls `escape(text)` from `mistune.util`checks `renderer._escape`calls `safe_entity(text)` or any other sanitisation helperThe `escape=True` flag only influences the main `HTMLRenderer` methods (`paragraph`, `heading`, `codespan`, etc.). Plugin render functions registered via `md.renderer.register()` receive the `renderer` instance but have no mechanism that enforces the escape contract - they must opt in manually, and `math.py` does not.PoC**Step 1 — Establish the baseline (escape=True works for plain HTML)**The script creates a markdown parser with `escape=True` and the math plugin enabled, then feeds it a raw `<script>` tag that is *not* inside math delimiters:Expected and actual output — the script tag is correctly escaped:This confirms `escape=True` is working for the normal render path.**Step 2 — Craft the exploit payload**Wrap the identical `<script>` payload inside inline math delimiters `$...$`. The content is token-extracted as `text` and handed to `render_inline_math()`:**Step 3 — Observe the bypass**Actual output — the script tag is emitted raw, unescaped:The `<script>` block is live inside the `<span class="math">` wrapper. Any browser that renders this HTML will execute `alert(document.cookie)`.**Step 4 — Block math variant (`$$...$$`)**The same bypass applies to block-level math. Payload:Output:The `onerror` handler fires as soon as the browser tries to load the non-existent image `x`.ScriptA verification script was written to test this issue. It creates a HTML page showing the bypass rendering in the browser.Example usage:Once the script is run, open `report_h1.html` in the browser and observe the behaviour.Impact| Dimension        | Assessment |
|------------------|-----------|
| **Confidentiality** | Attacker can exfiltrate session cookies, auth tokens, and any data visible to the victim's browser session |
| **Integrity**    | Attacker can mutate page content, inject phishing forms, redirect the user, or perform authenticated actions |
| **Availability** | Attacker can crash or freeze the page (denial-of-service to the user) |**Risk amplifier:** This is a *bypass* of an explicit security control. Developers who have audited their application and confirmed `escape=True` is set believe they have XSS protection. This vulnerability silently invalidates that assumption for every math-enabled parser instance, making it likely to be missed in code reviews and security audits.
CVSS v3.1: 6.1