CVE-2026-41238: DOMPurify: Prototype Pollution to XSS Bypass via CUSTOM_ELEMENT_HANDLING Fallback
Summary
DOMPurify versions 3.0.1 through 3.3.3 (latest) are vulnerable to a prototype pollution-based XSS bypass. When an application uses DOMPurify.sanitize() with the default configuration (no CUSTOMELEMENTHANDLING option), a prior prototype pollution gadget can inject permissive tagNameCheck and attributeNameCheck regex values into Object.prototype, causing DOMPurify to allow arbitrary custom elements with arbitrary attributes — including event handlers — through sanitization.
Affected Versions
- 3.0.1 through 3.3.3 (current latest) — all affected - 3.0.0 and all 2.x versions — NOT affected (used Object.create(null) for initialization, no || {} reassignment) - The vulnerable || {} reassignment was introduced in the 3.0.0→3.0.1 refactor - This is distinct from GHSA-cj63-jhhr-wcxv (USEPROFILES Array.prototype pollution, fixed in 3.3.2) - This is distinct from CVE-2024-45801 / GHSA-mmhx-hmjr-r674 (depth prototype pollution, fixed in 3.1.3)
Root Cause
In purify.js at line 590, during config parsing:
javascript CUSTOMELEMENTHANDLING = cfg.CUSTOMELEMENTHANDLING || {};
When no CUSTOMELEMENTHANDLING is specified in the config (the default usage pattern), cfg.CUSTOMELEMENTHANDLING is undefined, and the fallback {} is used. This plain object inherits from Object.prototype.
Lines 591-598 then check cfg.CUSTOMELEMENTHANDLING (the original config property) — which is undefined — so the conditional blocks that would set tagNameCheck and attributeNameCheck from the config are never entered.
As a result, CUSTOMELEMENTHANDLING.tagNameCheck and CUSTOMELEMENTHANDLING.attributeNameCheck resolve via the prototype chain. If an attacker has polluted Object.prototype.tagNameCheck and Object.prototype.attributeNameCheck with permissive values (e.g., /./), these polluted values flow into DOMPurify's custom element validation at lines 973-977 and attribute validation, causing all custom elements and all attributes to be allowed.
Impact
- Attack type: XSS bypass via prototype pollution chain - Prerequisites: Attacker must have a prototype pollution primitive in the same execution context (e.g., vulnerable version of lodash, jQuery.extend, query-string parser, deep merge utility, or any other PP gadget) - Config required: Default. No special DOMPurify configuration needed. The standard DOMPurify.sanitize(userInput) call is affected. - Payload: Any HTML custom element (name containing a hyphen) with event handler attributes survives sanitization
Proof of Concept
javascript // Step 1: Attacker exploits a prototype pollution gadget elsewhere in the application Object.prototype.tagNameCheck = /./; Object.prototype.attributeNameCheck = /./;
// Step 2: Application sanitizes user input with DEFAULT config const clean = DOMPurify.sanitize('<x-x onfocus=alert(document.cookie) tabindex=0 autofocus>');
// Step 3: "Sanitized" output still contains the event handler console.log(clean); // Output: <x-x onfocus="alert(document.cookie)" tabindex="0" autofocus="">
// Step 4: When injected into DOM, XSS executes document.body.innerHTML = clean; // alert() fires
Tested configurations that are vulnerable:
| Call Pattern | Vulnerable? | |---|---| | DOMPurify.sanitize(input) | YES | | DOMPurify.sanitize(input, {}) | YES | | DOMPurify.sanitize(input, { CUSTOMELEMENTHANDLING: null }) | YES | | DOMPurify.sanitize(input, { CUSTOMELEMENTHANDLING: {} }) | NO (explicit object triggers L591 path) |
Suggested Fix
Change line 590 from: javascript CUSTOMELEMENTHANDLING = cfg.CUSTOMELEMENTHANDLING || {};
To: javascript CUSTOMELEMENTHANDLING = cfg.CUSTOMELEMENTHANDLING || create(null);
The create(null) function (already used elsewhere in DOMPurify, e.g., in clone()) creates an object with no prototype, preventing prototype chain inheritance.
Alternative application-level mitigation:
Applications can protect themselves by always providing an explicit CUSTOMELEMENTHANDLING in their config:
javascript DOMPurify.sanitize(input, { CUSTOMELEMENTHANDLING: { tagNameCheck: null, attributeNameCheck: null } });
Timeline
- 2026-04-04: Vulnerability discovered during automated DOMPurify fuzzing research (Fermat project) - 2026-04-04: Confirmed in Chrome browser with DOMPurify 3.3.3 - 2026-04-04: Verified distinct from GHSA-cj63-jhhr-wcxv and CVE-2024-45801 - 2026-04-04: Advisory drafted, responsible disclosure initiated
Credit
https://github.com/trace37labs
Other sources
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Versions 3.0.1 through 3.3.3 are vulnerable to a prototype pollution-based XSS bypass. When an application uses DOMPurify.sanitize() with the default configuration (no CUSTOMELEMENTHANDLING option), a prior prototype pollution gadget can inject permissive tagNameCheck and attributeNameCheck regex values into Object.prototype, causing DOMPurify to allow arbitrary custom elements with arbitrary attributes — including event handlers — through sanitization. Version 3.4.0 fixes the issue.
— NVD
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/dompurifyto a version that resolves this vulnerability.Fixed in 3.4.0 - Upgrade
Upgrade
DOMPurifyto a version that resolves this vulnerability.Fixed in 3.4.0 - Configuration
In the application’s DOMPurify.sanitize() call, always pass an explicit CUSTOM_ELEMENT_HANDLING in the config so tagNameCheck and attributeNameCheck do not resolve via the prototype chain (avoid relying on the fallback when CUSTOM_ELEMENT_HANDLING is omitted or left undefined).
DOMPurify CUSTOM_ELEMENT_HANDLING = { tagNameCheck: <explicit function/regex>, attributeNameCheck: <explicit function/regex> }
Event History
Frequently Asked Questions
What is the severity of CVE-2026-41238?
CVE-2026-41238 is classified as a high severity vulnerability due to its potential exploitation via prototype pollution-based XSS bypass.
How do I fix CVE-2026-41238?
To fix CVE-2026-41238, upgrade DOMPurify to version 3.4.0 or later.
Which versions of DOMPurify are affected by CVE-2026-41238?
DOMPurify versions 3.0.1 through 3.3.3 are affected by CVE-2026-41238.
What is the impact of CVE-2026-41238?
The impact of CVE-2026-41238 includes the potential for cross-site scripting (XSS) attacks due to an XSS bypass.
What configurations are vulnerable in relation to CVE-2026-41238?
The default configuration of DOMPurify without the `CUSTOM_ELEMENT_HANDLING` option is vulnerable in relation to CVE-2026-41238.