{"id":"GHSA-55q2-fjhq-7xh7","summary":"DOMPurify: IN_PLACE hook removal leaves a detached subtree executable, causing XSS","details":"### 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\u003cdiv id=\"result\"\u003enot fired\u003c/div\u003e\n\u003cscript\u003e\nconst root = document.createElement('div');\nroot.innerHTML = `\n  \u003cfooter\u003e\n    \u003cimg src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\"\n         onload=\"result.textContent = 'XSS after sanitize'\"\u003e\n  \u003c/footer\u003e\n  \u003cdiv\u003esafe\u003c/div\u003e`;\n\nDOMPurify.setConfig({\n  ALLOWED_TAGS: ['div', '#text', 'footer'],\n  IN_PLACE: true\n});\nDOMPurify.addHook('uponSanitizeElement', node =\u003e {\n  if (node.tagName === 'FOOTER') node.remove();\n});\n\nDOMPurify.sanitize(root);\ndocument.body.append(root);\n\u003c/script\u003e\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 `\u003cdiv\u003esafe\u003c/div\u003e`, 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.","aliases":["CVE-2026-75838"],"modified":"2026-09-10T03:51:13.069736975Z","published":"2026-08-07T15:30:47Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-08-07T15:30:47Z","nvd_published_at":null,"cwe_ids":["CWE-79"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/cure53/DOMPurify/security/advisories/GHSA-55q2-fjhq-7xh7"},{"type":"WEB","url":"https://github.com/cure53/DOMPurify/pull/1557"},{"type":"WEB","url":"https://github.com/cure53/DOMPurify/commit/3067f7746769"},{"type":"PACKAGE","url":"https://github.com/cure53/DOMPurify"},{"type":"WEB","url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.13"}],"affected":[{"package":{"name":"dompurify","ecosystem":"npm","purl":"pkg:npm/dompurify"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.4.13"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 3.4.12","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-55q2-fjhq-7xh7/GHSA-55q2-fjhq-7xh7.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N"}]}