CVE-2026-65900
Summary
When DOMPurify is configured with both SAFE_FOR_TEMPLATES: true and RETURN_DOM: true (or IN_PLACE: true), an attacker can inject template expressions, such as ${evil}, {{evil}}, or <%evil%>, that survive the sanitization pass inside <template> element content. This bypasses the explicit purpose of SAFE_FOR_TEMPLATES, which is to prevent template engine evaluation of user-supplied content.
Note: The string output path is not affected. Only the DOM return paths (RETURN_DOM: true, RETURN_DOM_FRAGMENT: true, IN_PLACE: true) are vulnerable.
Description
Background
SAFE_FOR_TEMPLATES is designed to strip {{ }}, ${ }, and <% %> expressions from sanitized output so that downstream template engines do not evaluate user-controlled content. The feature operates through two mechanisms:
1. Per-node scrubbing (_sanitizeElements, src/purify.ts:1403), scrubs individual text nodes during the main sanitization walk.
2. Final normalization pass (_scrubTemplateExpressions, src/purify.ts:1115), calls node.normalize() to merge adjacent text nodes, then walks the merged nodes and strips any expressions that only appeared after merging.
The Gap
_scrubTemplateExpressions uses a standard NodeIterator rooted at the output body:
// src/purify.ts:1117
const walker = createNodeIterator.call(
node.ownerDocument || node,
node,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | ...,
null
);
Per the DOM specification, a NodeIterator does not descend into <template>.content. The template element's content is a separate DocumentFragment that lives outside the normal child-node tree. For the same reason, node.normalize() (called on line 1116) also does not normalize text nodes inside <template>.content.
This means the final normalization and scrub pass, the only pass that catches expressions formed *by merging split text nodes*, never runs on <template> content.
How Split Text Nodes Are Created
When DOMPurify removes a disallowed element with KEEP_CONTENT: true (the default), it moves
⚡ Watch CVE-2026-65900
Get an email if CVE-2026-65900 is added to CISA KEV, gains public exploit code, or a new advisory cites it — max one per day, one-click unsubscribe.
Exploitation outlook
- Low exploitation risk0.18% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 8% of all EPSS-scored CVEs.
Advisory coverage (2)
External references
Embed the live status
— this badge updates automatically when the KEV or exploit status changes. How to embed it →
[](https://www.csirts.com/cve/CVE-2026-65900)