---
title: "GHSA-cg2g-xh64-v4q7: DOMPurify SAFE_FOR_TEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output modes"
url: https://erroneo.us/security/ghsa/GHSA-cg2g-xh64-v4q7/
description: "GHSA-cg2g-xh64-v4q7 (critical, npm): DOMPurify SAFEFORTEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output…"
type: advisory
section: "Security Digest"
date_published: 2026-10-22
date_modified: 2026-10-22
publisher: erroneo.us
author: erroneo.us Security Desk
ghsa: "GHSA-cg2g-xh64-v4q7"
license: All rights reserved. Quote with attribution and a link.
---

# GHSA-cg2g-xh64-v4q7: DOMPurify SAFE_FOR_TEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output modes

## Affected packages

- npm `dompurify` >= 3.4.4, <= 3.4.16 → fixed: 3.4.17

## Details

Summary When DOMPurify is configured with SAFEFORTEMPLATES: true together with a DOM-returning mode (RETURNDOM: true, RETURNDOMFRAGMENT: true, or INPLACE: true), template expressions such as {{...}}, ${...}, and survive sanitization inside the text content of an attached shadow root. The post-walk safety net scrubTemplateExpressions (src/purify.ts:1717) was extended to recurse into .content, but it never recurses into attached shadow roots. An attacker who can supply a DOM node carrying a shadow root (the INPLACE and shadow-aware RETURNDOM surfaces) can split an expression across text nodes separated by disallowed elements; after those elements are removed the fragments become adjacent and serialize into a complete expression that a downstream template engine will evaluate. Root Cause SAFEFORTEMPLATES neutralizes template expressions through two mechanisms: a per-text-node strip during the main walk (stripTemplateExpressions, applied in sanitizeElements at src/purify.ts:2246), and a final post-walk pass (scrubTemplateExpressions, src/purify.ts:1717) that normalizes adjacent text nodes and re-strips expressions that only form after merging. The per-node strip cannot catch an expression that is split into individual fragments, because each fragment fails the delimiter regexes. The post-walk pass is the only mechanism that would catch the merged form, but it is structurally unable to reach shadow root content. Shadow root text nodes are therefore sanitized only per-node and never re-examined as a merged unit. Impact An attacker who controls DOM-node input containing an attached shadow root can inject template expressions that survive sanitization and are later evaluated by the application's template engine. In client-side contexts this yields cross-site scripting; in a server-side template context it can escalate to template injection. Exploitation requires non-default application configuration: the application must enable SAFEFORTEMPLATES and a DOM-returning mode, pass a DOM node that already has an attached shadow root, and feed the sanitized shadow root content into a template evaluator. The shadow root does not have to be built programmatically with attachShadow: in engines that support declarative shadow DOM, attacker-supplied markup containing

## Sources

- [GitHub Advisory GHSA-cg2g-xh64-v4q7](https://github.com/advisories/GHSA-cg2g-xh64-v4q7)
- [github.com](https://github.com/cure53/DOMPurify/security/advisories/GHSA-cg2g-xh64-v4q7)
- [github.com](https://github.com/cure53/DOMPurify/pull/1672)
- [github.com](https://github.com/cure53/DOMPurify/commit/a77a3e78b59f72fa4d1c83f22e121ad9cc7d3575)
- [github.com](https://github.com/cure53/DOMPurify/releases/tag/3.4.17)

---
Source page: https://erroneo.us/security/ghsa/GHSA-cg2g-xh64-v4q7/ · Security Digest: https://erroneo.us/security/ · More: https://erroneo.us/llms.txt
