DOMPurify: `IN_PLACE` mode trusts attacker-controlled `nodeName` on live non-form nodes, allowing script retention and XSS via attacker-supplied DOM objects
Date: 2026-06
Affected: npm/dompurify
Attack vector: xss
Category: vulnerability-disclosure
Severity: Low
CWE: CWE-79

## Summary When `DOMPurify.sanitize(root, { IN_PLACE: true })` is called on an attacker-supplied live DOM node, `DOMPurify` still trusts `currentNode.nodeName` for non-`form` nodes in the main `_sanitizeElements` pipeline. A real `<script>` child node whose observable `nodeName` is attacker-controlled can therefore be misclassified as an allowed element and retained. When the sanitized tree is inserted into a live document, the script executes. This affects current `3.4.6`. The recent `IN_PLACE` hardening work covers clobbered `form` handling and foreign-realm shadow/template traversal, but does not harden the main per-node element decision for hostile non-`form` live nodes. ## Affected - DOMPurify `3.4.6` - Any caller that does `DOMPurify.sanitize(node, { IN_PLACE: true })` on attacker-supplied live DOM nodes - Verified attacker-controlled node sources: - same-origin `iframe` → live node passed by reference - same-origin `window.open()` popup → live node passed by reference - same-origin foreign node adopted into the host document via `document.adoptNode(node)` and then sanitized in-place Not affected: - String-input `DOMPurify.sanitize(dirtyString)` ## Vulnerability details ### Code paths [A] — `_sanitizeElements` uses the instance-visible `nodeName` for the allow/forbid decision: ```ts const _sanitizeElements = function (currentNode: any): boolean { ... if (_isClobbered(currentNode)) { _forceRemove(currentNode); return true; } const tagName


Mitigations:

Architectural layers (MAESTRO):