Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessSubmit HTML with xlink:href javascript payload via user input
-
DeliveryCleaner(safe_attrs_only=False) fails to scrub namespaced attribute
-
ExploitMalicious SVG/MathML anchor stored and served to users
-
ExecutionVictim clicks rendered anchor
-
ImpactJavaScript executes in site origin, session hijack
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V1 - Encoding and Sanitization Output encoding, context-aware escaping
MITRE ATT&CK
2 techniques across 2 tactics: Initial Access, Execution The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
| Exploitation Conditions | Requires the target application to (1) sanitize untrusted HTML with lxml_html_clean's Cleaner (or lxml.html.clean.Cleaner) explicitly configured with safe_attrs_only=False - the decisive prerequisite, since the class default safe_attrs_only=True strips xlink:href and the bug does not fire; (2) store and re-render the sanitized output to other users; and (3) a victim must actively click the malicious SVG or MathML <a xlink:href> anchor (UI:R) in a browser that follows the SVG-anchor navigation spec. The payload must ride specifically on the namespaced xlink:href attribute (plain href is already filtered). Limiting factors: non-default configuration required, victim click required, and only javascript:/vbscript:/data:-style scheme payloads on xlink:href are affected. PR:N (unauthenticated) holds only for anonymous input surfaces; deployments that require authentication to submit HTML effectively raise the bar to PR:L. |
| Impact | The provided CVSS 3.1 vector (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N = 8.2 High) rates this as network-reachable, low-complexity, unauthenticated stored XSS requiring victim interaction with scope change - consistent with a sanitizer bypass. The most important limiting signal is not in the raw vector: exploitation is CONDITIONAL on the caller passing safe_attrs_only=False, a documented but non-default option; under the class default (safe_attrs_only=True) attribute allow-listing removes xlink:href before scheme scrubbing runs and the bug does not fire (verified in the advisory). This meaningfully narrows real-world exposure to the subset of deployments that opt out of the default. No EPSS score and no KEV listing were provided, so widespread automated exploitation is not evidenced; however a reliable POC exists, lowering the bar for opportunistic use. PR:N is defensible for anonymous input surfaces (comments, tickets); deployments that gate HTML submission behind authentication should re-score at PR:L (~7.6). Net: a genuine, easily-triggered XSS for the affected configuration, but not an internet-wide emergency - prioritize by whether your code sets safe_attrs_only=False. |
| Exploit ScenarioDerived | An attacker submits comment or support-ticket HTML containing <svg><a xlink:href="javascript:alert(document.cookie)">click</a></svg> to an application that sanitizes user HTML with Cleaner(safe_attrs_only=False) and later renders it to other users. The sanitizer passes the payload through untouched (a working POC demonstrates iterlinks() is blind to xlink:href); when a victim viewing the page clicks the SVG anchor, the script executes in the site's origin, enabling session/cookie theft. Because the attacker-controlled content is stored and re-served, this is persistent stored XSS. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Jul 08, 2026
Stored cross-site scripting in the lxml_html_clean Cleaner (≤ 0.4.4) and the bundled lxml legacy html.clean module (≤ 6.1.0) lets attackers smuggle javascript: URLs through HTML sanitization on the namespaced xlink:href attribute. When callers configure Cleaner with safe_attrs_only=False, the URL-scheme scrubber never inspects <a xlink:href="javascript:..."> inside SVG or MathML, so the payload survives and executes when a victim clicks the rendered anchor. Publicly available exploit code exists (a working reproducer ships with the advisory), but there is no public exploit identified as being used in active attacks and the issue is not in CISA KEV.
RemediationAI
No vendor-released patched version was identified at time of analysis (the report is a private/coordinating GHSA with a proposed fix, not a tagged release), so monitor advisory GHSA-4jhm-jv67-739f (https://github.com/fedora-python/lxml_html_clean/security/advisories/GHSA-4jhm-jv67-739f) and upgrade to the fixed lxml_html_clean release once published; the upstream fix adds 'xlink:href' to lxml.html.defs.link_attrs. As an immediate compensating control that does not depend on a release, revert to the safe default Cleaner(safe_attrs_only=True), which strips xlink:href before rendering - the trade-off is losing the custom data-/aria-/vendor attributes you enabled by setting safe_attrs_only=False. If you must keep safe_attrs_only=False, add a post-sanitization pass that deletes any namespaced URL attribute (xlink:href, and also validate each srcset candidate) whose value matches ^\s*(javascript|vbscript|data): case-insensitively and after entity/tab decoding, or switch security-sensitive sanitization to nh3, which the lxml_html_clean README recommends and which the advisory verified is not vulnerable to this primitive. Also normalize scheme-obfuscation variants (mixed-case JaVaScRiPt:, HTML-entity javascript:, embedded-tab java\tscript:) since those bypass naive string checks.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 2 packages in pypi, euvd
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The affected component is the pure-Python HTML sanitizer maintained in the lxml_html_clean package (the code extracted from lxml core in 5.2.0; lxml.html.clean is now a thin backward-compat shim). Cleaner strips dangerous URL schemes by walking links through rewrite_links() -> iterlinks(), which only yields attributes listed in lxml.html.defs.link_attrs. That allow-list (action, href, src, formaction, etc.) contains no namespaced/prefixed names, so xlink:href (and srcset) are never handed to the _remove_javascript_link filter. This is a textbook CWE-184 (Incomplete List of Disallowed Inputs) defect manifesting as CWE-79 (XSS): the sanitizer's disallow logic has a gap rather than a broken filter. Because libxml2 parses prefixed HTML attribute names verbatim (the literal string 'xlink:href', not Clark notation), the fix is a plain string-set extension. The root cause is identical to CVE-2021-28957, where the same link_attrs set was missing formaction.
Know when Python gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N
Network-reachable stored XSS with low attacker complexity and no auth on anonymous input; victim must click (UI:R) and script runs cross-origin in the render context (S:C), giving C:H/I:L.
Primary rating from Vendor (https://github.com/fedora-python/lxml_html_clean).
CVSS VectorVendor: https://github.com/fedora-python/lxml_html_clean
DescriptionCVE.org
lxml_html_clean.Cleaner does not strip javascript: URLs from namespaced URL attributes (xlink:href)
Reporter: Guillem Lefait <guillem@datamq.com> · Date: 2026-05-10 Affected: lxml ≤ 6.1.0 and lxml_html_clean ≤ 0.4.4 (latest stable) Confirmed against: lxml 6.1.0 + lxml_html_clean 0.4.4 on Python 3.13.5, 3.14.4, and 3.15.0a8 (libxml2 2.14.6 / 2.9.14 - bug is in pure-Python sanitizer logic, independent of the libxml2 backend) Root-cause class: same as CVE-2021-28957 (formaction missing from link_attrs)
Summary
Cleaner filters URL schemes (javascript:, vbscript:, …) by walking links via rewrite_links(), which delegates to iterlinks(), which only yields attributes named in lxml.html.defs.link_attrs. That allow-list contains no prefixed names (xlink:href) and no srcset. As a result, when Cleaner is configured with safe_attrs_only=False - a documented option for callers that want lenient attribute handling but still expect URL-scheme scrubbing - <a xlink:href="javascript:…"> survives sanitization untouched, and any browser that follows the SVG-anchor specification will execute the JavaScript when the rendered link is clicked.
CWE: CWE-79 (XSS), with CWE-184 (Incomplete List of Disallowed Inputs) as the underlying defect class.
Affected components
| Package | Versions tested | File / line |
|---|---|---|
lxml | 4.9.x, 5.2.1, 6.1.0 | src/lxml/html/defs.py:20 |
lxml | " | src/lxml/html/__init__.py:485-528 |
lxml_html_clean | 0.4.0 - 0.4.4 | lxml_html_clean/clean.py:348,576 |
The legacy lxml.html.clean module - bundled in lxml < 5.2.0 and still installable on newer versions via the lxml[html_clean] extra - shares the same bug.
Root cause
defs.link_attrs is a flat string set; the literal xlink:href is absent:
# lxml/html/defs.py
link_attrs = frozenset([
'action', 'archive', 'background', 'cite', 'classid',
'codebase', 'data', 'href', 'longdesc', 'profile', 'src',
'usemap', 'dynsrc', 'lowsrc', 'formaction',
])HtmlMixin.iterlinks() (lxml/html/__init__.py:526-528) only yields attributes whose key is in that set:
for attrib in link_attrs:
if attrib in attribs:
yield (el, attrib, attribs[attrib], 0)Cleaner.__call__ registers the URL-scheme filter via rewrite_links (lxml_html_clean/clean.py:348), which is a thin wrapper around iterlinks(). Because xlink:href is never yielded, _remove_javascript_link (clean.py:576) is never invoked for it.
Minimal reproducer
from lxml import html
from lxml_html_clean import Cleaner
for payload in (
'<svg><a xlink:href="javascript:alert(1)">x</a></svg>',
'<math><a xlink:href="javascript:alert(2)">y</a></math>',
):
tree = html.fromstring(payload)
Cleaner(safe_attrs_only=False)(tree)
print(html.tostring(tree).decode())
print(' iterlinks:', list(html.fromstring(payload).iterlinks()))
# <svg><a xlink:href="javascript:alert(1)">x</a></svg> ← unchanged
# iterlinks: [] ← link rewriter blind
# <math><a xlink:href="javascript:alert(2)">y</a></math> ← unchanged
# iterlinks: [] ← link rewriter blindBoth SVG and MathML scopes are vulnerable - same allow-list gap, both render anchors that browsers treat as navigable. Other lab-confirmed surviving variants (same scope, different scheme encoding): mixed-case (JaVaScRiPt:), HTML-entity (javascript:), embedded tab (java\tscript:).
Impact
A caller that uses Cleaner to neutralise untrusted HTML and chooses safe_attrs_only=False - typically because the application wants to allow custom data-/aria-/vendor attributes - will silently pass javascript: payloads carried on xlink:href through to victim renders. Stored XSS in any application that round-trips user-supplied HTML through this configuration. Reach is conditional on the safe_attrs_only=False toggle, but that is a documented public option; consumers reasonably expect URL-scheme scrubbing to be independent of attribute allow-listing.
Suggested fix
Extend link_attrs to include xlink:href. In HTML mode, lxml.html keeps prefixed attribute names verbatim - the parsed key is the literal string xlink:href, not a Clark-notation form - so the existing allow-list lookup is a plain string match. Same shape as the CVE-2021-28957 fix:
# lxml/html/defs.py
link_attrs = frozenset([
'action', 'archive', 'background', 'cite', 'classid',
'codebase', 'data', 'href', 'longdesc', 'profile', 'src',
'usemap', 'dynsrc', 'lowsrc', 'formaction',
+ 'xlink:href',
])This single change closes the reported XSS for both SVG <a xlink:href> and MathML <a xlink:href>. lxml_html_clean is the canonical home of the Cleaner code (881 lines); lxml.html.clean is a 21-line backward-compat shim (from lxml_html_clean import *) that picks up the fix automatically once link_attrs is updated upstream. Since the upstream change requires lxml maintainer action, see the alternative below if a self-contained patch in lxml_html_clean is preferred.
Alternative (in-package fix, no lxml coordination needed): add a namespaced-URL-attribute walk inside Cleaner.__call__ so the URL-scheme filter doesn't depend on link_attrs. Sketch:
# lxml_html_clean/clean.py - supplements rewrite_links() in __call__
_NS_URL_ATTRS = ('xlink:href',)
# extend as needed
_BAD_SCHEME = re.compile(r'^\s*(javascript|vbscript|data):', re.I)
for el in doc.iter():
for attr in _NS_URL_ATTRS:
if attr in el.attrib and _BAD_SCHEME.match(el.attrib[attr]):
del el.attrib[attr]This decouples the cleaner from the upstream link_attrs set and matches the security-ownership boundary established when the cleaner was extracted in lxml 5.2.0.
Defense in depth (optional, regardless of which fix path is taken):
- Also handle
srcset: the value is aurl 1x, url 2x, …descriptor list, so split on commas and validate each candidate URL. Not directly executable in current browsers, but closes the same gap. - Also accept Clark-notation forms (
{http://www.w3.org/1999/xlink}href) so XML-mode callers usinglxml.etreeget the same protection. HTML mode never produces this form, so not needed for the reported bug.
Severity
CVSS 3.1 base score: 8.2 / High - AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N (stored XSS; victim must click the SVG anchor; scope-changed because script executes in the rendering origin). PR:N reflects the common case where untrusted HTML enters the sanitizer from anonymous sources (comments, support tickets); deployments that gate writes behind authentication can score with PR:L (→ 7.6).
Severity is CONDITIONAL on the caller passing safe_attrs_only=False. With the class default (True), attribute allow-listing strips xlink:href before scheme scrubbing runs, and the bug does not fire - verified at HEAD: default-config Cleaner()(<svg><a xlink:href="javascript:…">x</a></svg>) → <svg><a>x</a></svg>.
Prior art / novelty
- CVE-2021-28957 (lxml 4.6.3) - same root cause, different attribute (
formaction). Fix was a one-line extension oflink_attrs. Direct precedent. - CVE-2022-34473 (Mozilla Sanitizer API) -
xlink:hrefURL bypass primitive in a different sanitizer. - Bleach (Mozilla, Python) explicitly handles the
xlinknamespace;enshrined/svg-sanitize(PHP) shipscleanXlinkHrefs(); DOMPurify scrubsxlink:hrefviaALLOWED_URI_REGEXP. nh3(the alternative recommended inlxml_html_clean's own README for security-sensitive use) is not vulnerable to this primitive - verified 2026-05-10 onnh3==0.3.5: with<svg>/<math>/<a>andxlink:hrefexplicitly added totags/attributes, both SVG and MathML payloads, all four scheme-encoding variants, are stripped (output e.g.<svg><a rel="noopener noreferrer">x</a></svg>).
Coordination
Filing as a private GHSA at fedora-python/lxml_html_clean - lxml_html_clean is the canonical maintainer of the Cleaner code (881 lines) and the security-responsible team since the lxml 5.2.0 split, where the cleaner was extracted out of lxml precisely so cleaner-security reports could land on the right team. The lxml side cannot be filed via GHSA (https://github.com/lxml/lxml/security/advisories/new returns 404 - private reporting is not enabled), so a parallel report has been emailed directly to the lxml maintainer for the upstream defs.link_attrs patch path. You're welcome to coordinate with them directly if you'd prefer the upstream fix over the in-package alternative above.
Happy to provide a draft patch or PR on either path. No bounty expected.
Blast Radius
ecosystem impact- 4 pypi packages depend on lxml-html-clean (3 direct, 1 indirect)
Ecosystem-wide dependent count for version 0.4.5.
Articles & Coverage 1
Executive ViewAI
lxml_html_clean (≤0.4.4) and lxml legacy html.clean (≤6.1.0) contain a stored cross-site scripting vulnerability that bypasses HTML sanitization when safe_attrs_only=False, enabling attackers to inject malicious JavaScript through SVG/MathML namespaced attributes. With publicly available exploit code in circulation, applications relying on these libraries face immediate risk of account compromise, data theft, and malware distribution when processing user-supplied HTML content. Organizations must immediately assess their dependency footprint and implement compensating controls pending patch availability.
No vendor-released patch identified at time of analysis. Interim mitigations: (1) Disable safe_attrs_only=False if application requirements permit; (2) Implement supplementary URL scheme filtering for SVG/MathML xlink:href attributes; (3) Apply Web Application Firewall rules to block javascript: scheme injection attempts; (4) Require manual content review for stored HTML in security-sensitive contexts.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Cross-Site Scripting occurs when an application accepts untrusted data and sends it to a web browser without proper validation or encoding.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Same weakness CWE-79 – Cross-site Scripting (XSS)
View allVendor StatusVendor
SUSE
Severity: Important| Product | Status |
|---|---|
| openSUSE Tumbleweed | Fixed |
EUVD-2026-63390
GHSA-4jhm-jv67-739f
NVD
CVE.org
EPSS
VulDB
Exploit-DB