{"id":"GHSA-55q2-fjhq-7xh7","title":"DOMPurify: IN_PLACE hook removal leaves a detached subtree executable, causing XSS","summary":"DOMPurify: IN_PLACE hook removal leaves a detached subtree executable, causing XSS","severity":"medium","cwe":["CWE-79"],"vendor":"dompurify","product":"dompurify","ecosystem":"npm","affected":["dompurify <= 3.4.12"],"patched":["dompurify 3.4.13"],"published":"2026-08-07","updated":"2026-08-07","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-55q2-fjhq-7xh7","references":[{"url":"https://github.com/cure53/DOMPurify/security/advisories/GHSA-55q2-fjhq-7xh7"},{"url":"https://github.com/cure53/DOMPurify/pull/1557"},{"url":"https://github.com/cure53/DOMPurify/commit/3067f7746769"},{"url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.13"},{"url":"https://github.com/advisories/GHSA-55q2-fjhq-7xh7"}],"tags":["ghsa","npm"],"ingestedAt":"2026-08-07T16:14:42.514Z","slug":"GHSA-55q2-fjhq-7xh7","body":"## Overview\n\n### Summary\n\nDuring `IN_PLACE` sanitization, a hook that removes an element can leave that element's detached descendants executable. A descendant image can retain its attacker-provided `onload` handler and fire after `sanitize()` returns, even though the returned root is clean and the image remains disconnected from the document.\n\n### Details\n\nIn DOMPurify 3.4.12, `_sanitizeElements()` in `src/purify.ts:1862-1904` runs the `beforeSanitizeElements` or `uponSanitizeElement` hook and returns immediately when the hook detached the current node. The return does not call `_neutralizeSubtree(currentNode)`.\n\nThe detached subtree is not added to `DOMPurify.removed`, so the post-walk `IN_PLACE` neutralization cannot reach it. If the browser queued a resource event while the application constructed the detached dirty root, a descendant can therefore retain its handler and execute after sanitization.\n\nThe hook only rejects the containing element and does not add or approve the event handler. DOMPurify's ordinary removal path de-arms the same queued event; only the hook-detachment early return skips the existing subtree neutralization.\n\n### PoC\n\nLoad the published `dompurify@3.4.12` `dist/purify.js` before this script in Chromium:\n\n```html\n<div id=\"result\">not fired</div>\n<script>\nconst root = document.createElement('div');\nroot.innerHTML = `\n  <footer>\n    <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\"\n         onload=\"result.textContent = 'XSS after sanitize'\">\n  </footer>\n  <div>safe</div>`;\n\nDOMPurify.setConfig({\n  ALLOWED_TAGS: ['div', '#text', 'footer'],\n  IN_PLACE: true\n});\nDOMPurify.addHook('uponSanitizeElement', node => {\n  if (node.tagName === 'FOOTER') node.remove();\n});\n\nDOMPurify.sanitize(root);\ndocument.body.append(root);\n</script>\n```\n\n`sanitize()` returns with no handler execution and the returned root contains only the safe `div`. After the event loop advances, the original image remains disconnected but its retained `onload` changes the page to `XSS after sanitize`.\n\nAs the claim-matched control, use the same detached input with `ALLOWED_TAGS: ['div', '#text']` and no hook. DOMPurify's ordinary removal path removes the original image's handler, the returned root is still `<div>safe</div>`, and the marker does not fire.\n\n### Impact\n\nIn an application that uses `IN_PLACE` with the documented element-removal hook pattern, an attacker who can supply HTML can execute JavaScript in the integrating application's origin after the application sanitizes and renders that content.\n\nThe required non-default configuration is `IN_PLACE` plus a hook that removes a containing element. The hook does not add or approve the event handler, and the dirty root never needs to be connected before sanitization.\n\n### Suggested fix\n\nReuse the existing `_neutralizeSubtree(currentNode)` helper before returning from both hook-detachment branches in `_sanitizeElements()`. Add regressions for `beforeSanitizeElements` and `uponSanitizeElement` that retain a reference to a descendant resource element and verify that its event handler is removed after the hook detaches its ancestor.\n\n## Affected packages\n\n- `dompurify <= 3.4.12`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `dompurify 3.4.13`","depth":"sunlit","depthScore":28,"depthScoreParts":{"impact":27.5,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}