Friday 23 October 2026
Security

DOMPurify shadow root gap lets template expressions survive sanitization

A GitHub advisory says DOMPurify's post-walk scrub misses attached shadow roots, allowing split template expressions to re-form and execute when SAFE_FOR_TEMPLATES and DOM-returning modes are used. No fix is available.

Illustration: DOMPurify shadow root vulnerability
Illustration: Five Nines

GitHub advisory GHSA-cg2g-xh64-v4q7 warns that template expressions can survive DOMPurify sanitization when SAFE_FOR_TEMPLATES is enabled with a DOM-returning mode and the input DOM node carries an attached shadow root. The advisory states that affected versions are 3.4.4 through 3.4.16 and that no fixed release is available.[1]

DOMPurify neutralizes template expressions such as {{...}}, ${...}, and <%...%> in two steps: per-text-node stripping during the main walk and a post-walk pass that merges adjacent text nodes and removes any newly formed expressions. The post-walk pass, _scrubTemplateExpressions, recurses into template elements but not into attached shadow roots, according to the advisory. An attacker can therefore split an expression across text nodes separated by a disallowed element. Sanitization removes the disallowed element, leaving the fragments adjacent. When an application later reads the shadow root's text content and feeds it to a template engine, the fragments serialize into a complete expression and execute.[1]

The advisory says exploitation requires a non-default configuration: SAFE_FOR_TEMPLATES enabled, a DOM-returning mode such as RETURN_DOM, RETURN_DOM_FRAGMENT, or IN_PLACE, and passing a DOM node that already has an attached shadow root. In client-side contexts this yields cross-site scripting; in server-side template contexts it can escalate to template injection. The advisory notes that declarative shadow DOM, where attacker-supplied markup contains <template shadowrootmode="open"> parsed with setHTMLUnsafe, can also produce the attached shadow root, so a plain HTML string can reach the gap under that configuration.[1]

The proof of concept builds a host element with an attached shadow root, adds text nodes '$' and '{alert(document.domain)}' separated by an object element, and sanitizes in place with SAFE_FOR_TEMPLATES. After the object is removed, the shadow root text becomes ${alert(document.domain)}, which a downstream evaluator would run. The advisory recommends that the fix apply the post-walk normalize-and-rescrub to shadow roots, including nested shadow roots and shadow roots inside template content, for all three expression dialects.[1]

Reported by GitHub user @manus-pi, the advisory says the gap is an unfixed case on a distinct remediation boundary, not a bypass of the earlier template-content fix from CVE-2026-65900. Administrators should avoid enabling SAFE_FOR_TEMPLATES with DOM-returning modes on untrusted DOM nodes that may contain shadow roots until a patched release is available.[1]

Lena Vasquez

Lena Vasquez covers vulnerabilities, breaches and identity for Five Nines. She writes from advisories, vendor bulletins and published reporting.

Corrections. Report an error to corrections@thefivenines.com. We list substantive corrections on the corrections page.