CVE-2026-44899
Mistune Image Directive CSS Injection Vulnerability
CVE-2026-44899 is a medium vulnerability (CVSS 4.7) in Lepture Mistune, published May 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.23%, the 13th percentile. Fixed in mistune 3.2.1.
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
4.7
Mistune Image Directive CSS Injection Vulnerability
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
CVE-2026-44899- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-31992
4.7
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
EUVD-2026-31992- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-ccfx-mfmx-2fx9
4.7
Mistune Image Directive CSS Injection Vulnerability
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
_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 check
attrs["width"] = width
And in render_block_image():
if width:
if width.isdigit():
img += ' width="' + width + '"' # safe: integer → HTML attribute
else:
style += "width:" + width + ";" # UNSAFE: non-integer → raw style value
The isdigit() branch correctly uses an HTML attribute for plain integers. The else branch assumes that anything that passed _num_re.match() is a safe CSS length like 100px or 50%. However, because the regex is prefix-only, 100vw;height:100vh;position:fixed;... also passes, and the entire string lands in style= unmodified.
PoC
Step 1 — Establish the baseline (safe plain-integer dimensions)
The script creates a parser with escape=True, FencedDirective, and the Image plugin. A safe image directive is rendered with integer width and height:
md = create_markdown(escape=True, plugins=[FencedDirective([Image()])])
bl_src = (
"```{image} photo.jpg\n"
":width: 400\n"
":height: 300\n"
":alt: safe image\n"
"```\n"
)
bl_out = str(md(bl_src))
Expected and actual output — clean width= and height= HTML attributes, no style=:
<div class="block-image"><img src="photo.jpg" alt="safe image" width="400" height="300" /></div>
Step 2 — Understand why non-integer widths go into style=
When width is not a plain integer (e.g., 100px), width.isdigit() returns False, so the render path falls through to style += "width:" + width + ";". This is the intended mechanism for CSS-unit dimensions. The flaw is that _num_re.match() lets far more than CSS units through.
Step 3 — Craft the exploit payload
Provide a :width: value that begins with a valid number (satisfying _num_re.match()) but appends an entire CSS attack chain after it:
:width: 100vw;height:100vh;position:fixed;top:0;left:0;z-index:9999;background-color:#e11d48;outline:8px solid #facc15;color:#fff;opacity:.93
100vw— starts with1, passes_num_re.match(); also sets the width to full viewport width;height:100vh— overrides height to full viewport height;position:fixed— lifts element out of document flow, fixed to the browser viewport;top:0;left:0— anchors overlay to the top-left corner;z-index:9999— places it above all other page content;background-color:#e11d48— fills the overlay with vivid crimson;outline:8px solid #facc15— adds a bright…
- Record
GHSA-ccfx-mfmx-2fx9- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
4.7
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
CVE-2026-44899- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
4.7
Mistune Image Directive CSS Injection Vulnerability
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
CVE-2026-44899- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-44899
6.1
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
DEBIAN-CVE-2026-44899- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
Show 6 moreShow less
OSVGHSA-ccfx-mfmx-2fx9
4.7
Mistune Image Directive CSS Injection Vulnerability
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
_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 check
attrs["width"] = width
And in render_block_image():
if width:
if width.isdigit():
img += ' width="' + width + '"' # safe: integer → HTML attribute
else:
style += "width:" + width + ";" # UNSAFE: non-integer → raw style value
The isdigit() branch correctly uses an HTML attribute for plain integers. The else branch assumes that anything that passed _num_re.match() is a safe CSS length like 100px or 50%. However, because the regex is prefix-only, 100vw;height:100vh;position:fixed;... also passes, and the entire string lands in style= unmodified.
PoC
Step 1 — Establish the baseline (safe plain-integer dimensions)
The script creates a parser with escape=True, FencedDirective, and the Image plugin. A safe image directive is rendered with integer width and height:
md = create_markdown(escape=True, plugins=[FencedDirective([Image()])])
bl_src = (
"```{image} photo.jpg\n"
":width: 400\n"
":height: 300\n"
":alt: safe image\n"
"```\n"
)
bl_out = str(md(bl_src))
Expected and actual output — clean width= and height= HTML attributes, no style=:
<div class="block-image"><img src="photo.jpg" alt="safe image" width="400" height="300" /></div>
Step 2 — Understand why non-integer widths go into style=
When width is not a plain integer (e.g., 100px), width.isdigit() returns False, so the render path falls through to style += "width:" + width + ";". This is the intended mechanism for CSS-unit dimensions. The flaw is that _num_re.match() lets far more than CSS units through.
Step 3 — Craft the exploit payload
Provide a :width: value that begins with a valid number (satisfying _num_re.match()) but appends an entire CSS attack chain after it:
:width: 100vw;height:100vh;position:fixed;top:0;left:0;z-index:9999;background-color:#e11d48;outline:8px solid #facc15;color:#fff;opacity:.93
100vw— starts with1, passes_num_re.match(); also sets the width to full viewport width;height:100vh— overrides height to full viewport height;position:fixed— lifts element out of document flow, fixed to the browser viewport;top:0;left:0— anchors overlay to the top-left corner;z-index:9999— places it above all other page content;background-color:#e11d48— fills the overlay with vivid crimson;outline:8px solid #facc15— adds a bright…
- Record
GHSA-ccfx-mfmx-2fx9- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVPYSEC-2026-2209
6.1
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
PYSEC-2026-2209- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-44899
4.7
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
UBUNTU-CVE-2026-44899- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
OSVAZL-88406 No score
CVE-2026-44899 affecting package python-mistune for versions less than 3.2.1-1
Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.2.1, the Image directive plugin validates the :width: and :height: options with a regex compiled as _num_re = re.compile(r"^\d+(?:.\d*)?"). 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. This vulnerability is fixed in 3.2.1.
- Record
AZL-88406- Updated
- 2026-11-02
OSVopenSUSE-SU-2026:20827-1 No score
Security update for python-mistune
This update for python-mistune fixes the following issues
- CVE-2026-33079: ReDoS in
LINK_TITLE_REcan lead to denial of service via a crafted Markdown (bsc#1264347). - CVE-2026-33441: processing of malformed reference links can lead to excessive resource consumption and denial of service (bsc#1264752).
- CVE-2026-44708: improper HTML escaping in the math plugin can lead to XSS (bsc#1264751).
- CVE-2026-44896: improper escaping in
render_figurecan lead to attribute injection and XSS (bsc#1264754). - CVE-2026-44897: improper sanitization of user-controlled input in
HTMLRenderer.headingcan lead to XSS (bsc#1264750). - CVE-2026-44898: improper sanitization of user-supplied HTML input in
render_toc_ulcan lead to XSS (bsc#1265052). - CVE-2026-44899: improper input verification in Image directive plugin and improper escaping in
render_block_imagecan lead to CSS injection (bsc#1265053).
- Record
openSUSE-SU-2026:20827-1- Updated
- 2026-11-02
OSVSUSE-SU-2026:21858-1 No score
Security update for python-mistune
This update for python-mistune fixes the following issues
- CVE-2026-33079: ReDoS in
LINK_TITLE_REcan lead to denial of service via a crafted Markdown (bsc#1264347). - CVE-2026-33441: processing of malformed reference links can lead to excessive resource consumption and denial of service (bsc#1264752).
- CVE-2026-44708: improper HTML escaping in the math plugin can lead to XSS (bsc#1264751).
- CVE-2026-44896: improper escaping in
render_figurecan lead to attribute injection and XSS (bsc#1264754). - CVE-2026-44897: improper sanitization of user-controlled input in
HTMLRenderer.headingcan lead to XSS (bsc#1264750). - CVE-2026-44898: improper sanitization of user-supplied HTML input in
render_toc_ulcan lead to XSS (bsc#1265052). - CVE-2026-44899: improper input verification in Image directive plugin and improper escaping in
render_block_imagecan lead to CSS injection (bsc#1265053).
- Record
SUSE-SU-2026:21858-1- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
mistunePyPI |
3.2.0 to 3.2.0 | 3.2.1 |
python-mistuneAzure Linux:3 |
before 3.2.1-1 | 3.2.1-1 |
mistuneDebian:12 |
All versions, plus listed versions 2.0.4-12.0.4-23.0.2-13.0.2-23.1.3-13.1.4-13.3.4-1 |
None known |
mistuneDebian:13 |
All versions, plus listed versions 3.1.3-13.1.4-13.3.4-1 |
None known |
mistuneDebian:14 |
All versions, plus listed versions 3.1.3-13.1.4-13.3.4-1 |
None known |
mistunePyPI |
3.2.0 up to 3.2.1 | 3.2.1 |
mistunePyPI |
before 3.2.1, plus listed versions 0.1.00.2.00.3.00.3.10.4 Show 45 moreShow less0.4.10.50.5.10.60.70.7.10.7.20.7.30.7.40.80.8.10.8.20.8.30.8.42.0.02.0.0a12.0.0a22.0.0a32.0.0a42.0.0a52.0.0a62.0.0rc12.0.12.0.22.0.32.0.42.0.52.1.03.0.03.0.0a13.0.0a23.0.0a33.0.0rc13.0.0rc23.0.0rc33.0.0rc43.0.0rc53.0.13.0.23.1.03.1.13.1.23.1.33.1.43.2.0 |
3.2.1 |
mistuneUbuntu:16.04:LTS |
All versions, plus listed versions 0.6-20.7.1-1 |
None known |
mistuneUbuntu:18.04:LTS |
All versions, plus listed versions 0.7.4-10.8-10.8.1-10.8.3-2 |
None known |
mistuneUbuntu:20.04:LTS |
All versions, plus listed versions 0.8.4-10.8.4-2 |
None known |
Show 10 moreShow less
| Package | Affected | Fixed |
|---|---|---|
mistuneUbuntu:22.04:LTS |
All versions, plus listed versions 0.8.4-40.8.4-52.0.0-1+really0.8.4-1 |
None known |
mistune0Ubuntu:22.04:LTS |
All versions, plus listed versions 0.8.4-10.8.4-2 |
None known |
mistuneUbuntu:24.04:LTS |
All versions, plus listed versions 2.0.4-23.0.2-1 |
None known |
mistune0Ubuntu:24.04:LTS |
All versions, plus listed versions 0.8.4-2 |
None known |
mistuneUbuntu:25.10 |
All versions, plus listed versions 3.0.2-23.1.3-1 |
None known |
mistune0Ubuntu:25.10 |
All versions, plus listed versions 0.8.4-4 |
None known |
mistuneUbuntu:26.10 |
All versions, plus listed versions 3.1.4-1 |
None known |
mistune0Ubuntu:26.10 |
All versions, plus listed versions 0.8.4-6 |
None known |
mistuneUbuntu:26.04:LTS |
All versions, plus listed versions 3.1.3-13.1.4-1 |
None known |
mistune0Ubuntu:26.04:LTS |
All versions, plus listed versions 0.8.4-40.8.4-50.8.4-6 |
None known |
History
| When | Changed | Sources |
|---|---|---|
| 2026-11-02 | created | CVE List, EUVD, GitHub Advisories, NVD, OSV |
References
github.com/lepture/mistune/security/advisories/GHSA-ccfx-mfmx-2fx9
github.com/lepture/mistune/releases/tag/v3.2.1
nvd.nist.gov/vuln/detail/CVE-2026-44899
github.com/advisories/GHSA-ccfx-mfmx-2fx9
github.com/CVEProject/cvelistV5/tree/main/cves/2026/44xxx/CVE-2026-44899.json
security-tracker.debian.org/tracker/CVE-2026-44899
Show 18 moreShow less
github.com/lepture/mistune
suse.com/support/update/announcement/2026/suse-su-202621858-1
bugzilla.suse.com/1264347
bugzilla.suse.com/1264750
bugzilla.suse.com/1264751
bugzilla.suse.com/1264752
bugzilla.suse.com/1264754
bugzilla.suse.com/1265052
bugzilla.suse.com/1265053
suse.com/security/cve/CVE-2026-33079
suse.com/security/cve/CVE-2026-33441
suse.com/security/cve/CVE-2026-44708
suse.com/security/cve/CVE-2026-44896
suse.com/security/cve/CVE-2026-44897
suse.com/security/cve/CVE-2026-44898
suse.com/security/cve/CVE-2026-44899
ubuntu.com/security/CVE-2026-44899
cve.org/CVERecord?id=CVE-2026-44899
Left out here: the rest of 2 source descriptions. The JSON record has them.