Mend.io Vulnerability Database
The largest open source vulnerability database
What is a Vulnerability ID?
New vulnerability? Tell us about it!
CVE-2026-41239
Published:April 23, 2026
Updated:April 25, 2026
Summary | Field | Value | |:------|:------| | Severity | Medium | | Affected | DOMPurify "main" at ""883ac15"" (https://github.com/cure53/DOMPurify/tree/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6), introduced in v1.0.10 (""7fc196db"" (https://github.com/cure53/DOMPurify/commit/7fc196db0b42a0c360262dba0cc39c9c91bfe1ec)) | "SAFE_FOR_TEMPLATES" strips "{{...}}" expressions from untrusted HTML. This works in string mode but not with "RETURN_DOM" or "RETURN_DOM_FRAGMENT", allowing XSS via template-evaluating frameworks like Vue 2. Technical Details DOMPurify strips template expressions in two passes: 1. Per-node — each text node is checked during the tree walk (""purify.ts:1179-1191"" (https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1179-L1191)): // pass #1: runs on every text node during tree walk if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { content = currentNode.textContent; content = content.replace(MUSTACHE_EXPR, ' '); // {{...}} -> ' ' content = content.replace(ERB_EXPR, ' '); // <%...%> -> ' ' content = content.replace(TMPLIT_EXPR, ' '); // ${... -> ' ' currentNode.textContent = content; } 2. Final string scrub — after serialization, the full HTML string is scrubbed again (""purify.ts:1679-1683"" (https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1679-L1683)). This is the safety net that catches expressions that only form after the DOM settles. The "RETURN_DOM" path returns before pass #2 ever runs (""purify.ts:1637-1661"" (https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1637-L1661)): // purify.ts (simplified) if (RETURN_DOM) { // ... build returnNode ... return returnNode; // <-- exits here, pass #2 never runs } // pass #2: only reached by string-mode callers if (SAFE_FOR_TEMPLATES) { serializedHTML = serializedHTML.replace(MUSTACHE_EXPR, ' '); } return serializedHTML; The payload "{<foo></foo>{constructor.constructor('alert(1)')()}<foo></foo>}" exploits this: 3. Parser creates: "TEXT("{")" → "<foo>" → "TEXT("{payload}")" → "<foo>" → "TEXT("}")" — no single node contains "{{", so pass #1 misses it 4. "<foo>" is not allowed, so DOMPurify removes it but keeps surrounding text 5. The three text nodes are now adjacent — ".outerHTML" reads them as "{{payload}}", which Vue 2 compiles and executes
Affected Packages
dompurify (CDN_JS):
Affected version(s) >=1.0.10 <3.4.0
Fix Suggestion:
Update to version 3.4.0
dompurify (NPM):
Affected version(s) >=1.0.10 <3.4.0
Fix Suggestion:
Update to version 3.4.0
Additional Notes
The description of this vulnerability differs from MITRE.
Do you need more information?
Contact Us
CVSS v4
Base Score:
7.6
Attack Vector
NETWORK
Attack Complexity
HIGH
Attack Requirements
NONE
Privileges Required
NONE
User Interaction
PASSIVE
Vulnerable System Confidentiality
HIGH
Vulnerable System Integrity
HIGH
Vulnerable System Availability
NONE
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
6.8
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
REQUIRED
Scope
UNCHANGED
Confidentiality
HIGH
Integrity
HIGH
Availability
NONE
Weakness Type (CWE)
Improper Validation of Unsafe Equivalence in Input
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
EPSS
Base Score:
0.05