Mistune Image Directive CSS Injection Vulnerability
Date: 2026-05
Affected: pip/mistune
Attack vector: phishing
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-44708
CVE-2026-44897
CVE-2026-44898
CVE-2026-44899
CWE: CWE-79
CVSS: 4.7 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N)

## Summary The Image directive plugin validates the `:width:` and `:height:` options with a regex compiled as `_num_re = re.compile(r"^\d+(?:\.\d*)?")`. This pattern is applied via `re.match()` (which anchors only at the **start** of the string, not the end). Any value that begins with one or more digits passes validation, regardless of what follows. When the validated value is not a plain integer, `render_block_image()` inserts it directly into a `style="width:...;"` or `style="height:...;"` attribute. Because the value was accepted by the prefix-only regex, any CSS after the leading digits reaches the `style=` attribute verbatim and without escaping. An attacker can therefore inject an arbitrary chain of CSS properties — including `position:fixed`, `background-color`, `z-index`, `outline`, and `opacity` — using nothing more than a single `:width:` option in a fenced image directive. The resulting element can visually cover the entire browser viewport, enabling full-page phishing overlays and UI redressing attacks. ## Details **File:** `src/mistune/directives/image.py` ```python _num_re = re.compile(r"^\d+(?:\.\d*)?") # no $ anchor — prefix match only def _parse_attrs(options): height = options.get("height") width = options.get("width") if height and _num_re.match(height): # passes if value STARTS with a digit attrs["height"] = height # full value stored, not just digits if width and _num_re.match(width): # same — prefix-only c


Mitigations:

Architectural layers (MAESTRO):