Security Digest · Critical advisory (no CVE assigned)

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

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.

CVSS 9.2 Critical GitHub-reviewed · critical

Should you act?

  • Only if you depend on one of the packages below, directly or transitively, in a vulnerable version. Check your lockfile with npm audit (or: npm ls <package>).
  • No CVE has been assigned, so it will not appear in CVE-based scanners that ignore GitHub advisories. npm audit, pip-audit and Dependabot do read GitHub advisories.

Affected packages

EcosystemPackageVulnerableFirst fixed
npmdompurify>= 3.4.4, <= 3.4.163.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

CVSS: 9.2 (v4.0) CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N

Sources and references

  1. GitHub Advisory GHSA-cg2g-xh64-v4q7 github.com
  2. github.com github.com
  3. github.com github.com
  4. github.com github.com
  5. github.com github.com

Critical npm advisories · Today’s Security Digest