CVE-2021-43818 is a cross-site scripting (XSS) bypass affecting lxml.html.clean, the HTML sanitization module bundled with the popular Python XML/HTML toolkit lxml. The Cleaner class in this module exists for exactly one purpose: to take untrusted, attacker-influenced HTML (forum posts, comments, RSS/Atom feed bodies, HTML email, wiki content) and strip out anything capable of running script in a browser. CVE-2021-43818 demonstrated that crafted SVG markup could slip through that sanitization step, allowing script execution in output that applications believed had already been "cleaned." For any Python service that pipes user-supplied HTML through Cleaner before rendering it to other users, that's a direct path from untrusted input to stored or reflected XSS.
The bug is a good reminder of a pattern that shows up again and again in supply chain security: a single, widely-imported library function becomes the de facto security boundary for dozens of unrelated applications, and a flaw in that one function propagates silently through every dependent codebase until someone bumps a version pin.
Affected Versions and Components
The vulnerable component is lxml.html.clean.Cleaner, the HTML/XML sanitizer shipped as part of the lxml package. lxml itself is one of the most widely used libraries in the Python ecosystem — it underlies HTML/XML parsing in projects like BeautifulSoup (via the lxml parser backend), Scrapy, python-docx-adjacent tooling, RSS/Atom feed parsers, PDF/HTML converters, and countless internal admin tools that render "safe" HTML back to users.
The fix landed in lxml 4.6.5. Any project pinned to an lxml release prior to 4.6.5 that relies on Cleaner (directly, or indirectly through a wrapper library) to sanitize untrusted markup containing or permitting SVG content should be treated as exposed. Because lxml is very often a transitive dependency — pulled in by an HTML-parsing library rather than declared directly in requirements.txt — many teams that were affected didn't realize they shipped a vulnerable version at all. That's the classic supply chain angle: the vulnerable code isn't in your repo, it's two or three dependency hops away.
It's also worth noting this wasn't lxml's first Cleaner bypass. A related sanitizer-bypass issue (CVE-2020-27783, via crafted CSS) had been fixed in 4.6.3 only a little earlier. The lxml maintainers themselves have long cautioned in the project's own documentation that Cleaner is a best-effort sanitizer, not a guaranteed XSS boundary — a caveat that's easy to miss when a function named "Cleaner" is sitting in the middle of your rendering pipeline looking authoritative.
Severity: CVSS, EPSS, and KEV Context
CVE-2021-43818 is classified as a cross-site scripting weakness (CWE-79), and public vulnerability databases score it in the medium severity range under CVSS v3.x — consistent with typical reflected/stored XSS characteristics: network-exploitable, low attack complexity, but requiring the victim to view attacker-controlled content that has passed through the sanitizer, and impact limited to script execution in the victim's browser session rather than direct server compromise. We'd encourage readers to pull the current authoritative score from NVD rather than relying on a single cited number here, since CVSS assignments for library-level XSS issues are sometimes revised after initial publication.
This CVE does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, and EPSS exploitation-probability scores for it have historically stayed low. That's not surprising: exploitation isn't a matter of hitting an internet-facing service directly — it requires an application that specifically feeds attacker-controlled HTML/SVG through Cleaner and then trusts the output. That's a common pattern, but it's an indirect one, and indirect application-logic-dependent bugs generally show lower EPSS scores than remotely triggerable network-service flaws even when the underlying risk to a specific vulnerable app is serious.
The practical takeaway: low EPSS and absence from KEV do not mean "ignore this." They mean mass, opportunistic internet scanning isn't the primary threat model — targeted abuse of applications that render user HTML is. If your product has a comment box, a rich-text editor, an email-rendering feature, or anything that accepts and later displays HTML from one user to another, this is exactly the kind of CVE that matters more to you than its raw score suggests.
Timeline
- Prior history: lxml's
Cleanerhad already been the subject of at least one earlier sanitizer-bypass disclosure (CVE-2020-27783, addressed in the 4.6.3 release), establishing a pattern of recurring bypass techniques against the module's tag/attribute blocklist approach. - Discovery and disclosure: Researchers identified that SVG markup could be crafted to survive
Cleaner's default sanitization rules and still execute script when rendered by a browser — because SVG's namespace and event-handling surface (elements like<animate>, attributes such asxlink:href, and SVG-specific event handlers) is broader and structured differently than plain HTML's, and wasn't fully accounted for by the Cleaner's blocklist logic. - Fix released: The maintainers shipped lxml 4.6.5 in late 2021, hardening
Cleaner's handling of SVG content and closing the specific bypass path. CVE-2021-43818 was published to track the issue and the fix. - Downstream propagation: As with most sanitizer-library CVEs, the practical remediation timeline for any given organization depends entirely on how quickly
lxmlgets bumped inrequirements.txt,Pipfile.lock,poetry.lock, or transitively through whichever HTML-parsing dependency pulled it in.
Remediation Steps
- Upgrade lxml to 4.6.5 or later. This is the primary fix. If your project pins
lxmldirectly, bump the version constraint and re-lock. If it's a transitive dependency, checkpip show lxml/ your lockfile resolution to confirm which version actually gets installed, since a parent package's own pin can keep you on a vulnerable release even after you update your top-level requirements. - Audit for transitive exposure. Search your dependency tree (
pip list,pipdeptree, or your SBOM) forlxmlregardless of whether you import it directly. Libraries like BeautifulSoup'slxmlbackend, feed parsers, and document-conversion tools frequently depend on it without callers realizing. - Don't treat
Cleaneras a complete XSS boundary. Even patched,Cleaneris a best-effort HTML sanitizer, not a formally verified security control. Where the content you're sanitizing is high-risk (user-generated HTML rendered to other users, HTML email rendering, etc.), pair it with defense in depth: strict tag/attribute allowlists, explicit denial of<svg>and<math>embedding if your application has no legitimate use for them, and a strong Content-Security-Policy on the pages where the sanitized output is rendered. - Add output-side mitigations. A restrictive CSP (blocking inline scripts and restricting
script-src) limits the blast radius of any sanitizer bypass, past or future, including ones not yet discovered. - Re-test sanitization logic after upgrading. If you have internal test suites asserting "this malicious payload gets stripped," extend them with SVG-based payloads specifically, and re-run them against the new lxml version to confirm the fix actually closes your application's specific usage pattern.
- Track this as a supply chain finding, not just a code finding. Because the vulnerable logic lives inside a widely-shared dependency, the same fix needs to be verified across every service, script, and internal tool in your organization that touches
lxml, not just the one repository where someone happened to notice it.
How Safeguard Helps
CVE-2021-43818 is a textbook example of why dependency-level visibility matters as much as source-code review. The vulnerable logic sits inside a transitive dependency that most engineering teams never audit directly, in a function whose name (Cleaner) implies a security guarantee it doesn't fully provide, and its risk is entirely dependent on how each downstream application uses it.
Safeguard's software composition analysis continuously inventories every direct and transitive Python dependency across your repositories — including cases like this one, where lxml arrives indirectly through an HTML-parsing or feed-processing library rather than as a declared top-level requirement. When a CVE like this is published, Safeguard flags every affected service automatically, so security and engineering teams don't have to manually chase down which of dozens of microservices happen to import a vulnerable Cleaner three dependency hops deep.
Beyond detection, Safeguard correlates CVE data with real-world exploitation signal — CISA KEV status, EPSS trends, and reachability of the vulnerable code path — so teams can prioritize fixes based on actual risk to their specific environment rather than raw CVSS numbers alone. For a bug like CVE-2021-43818, where the danger is concentrated in applications that render user-controlled HTML back to other users, that context is the difference between an accurate, targeted remediation plan and a noisy "update everything" ticket queue. Safeguard also tracks remediation status over time, giving teams a clear, auditable record that every affected service has been upgraded past 4.6.5 — turning a one-off patch into verifiable, continuous supply chain assurance.