CVE-2026-109344
DOMPurify from 3.4.13 before 3.4.16 DOM XSS via IN_PLACE afterSanitize Hooks
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Active
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.1
DOMPurify from 3.4.13 before 3.4.16 DOM XSS via IN_PLACE afterSanitize Hooks
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
CVE-2026-109344- CVSS 4.0
- 5.1 Medium
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 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-97646
5.1
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
EUVD-2026-97646- CVSS 4.0
- 5.1 Medium
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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-59fh-8xxf-p69h
5.1
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because...
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
GHSA-59fh-8xxf-p69h- CVSS 4.0
- 5.1 Medium
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
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-p98j-92pf-mc4p
2.3
DOMPurify: IN_PLACE: node-removing afterSanitize hook leaves detached subtree event handlers armed, causing DOM XSS
Summary
In IN_PLACE mode DOMPurify sanitizes the caller's live DOM subtree directly. To close a known hazard (GHSA-55q2-fjhq-7xh7), the library neutralizes any node a hook detaches during sanitization by stripping its subtree's non-allow-listed attributes, but this neutralization is wired only into the beforeSanitizeElements and uponSanitizeElement hook sites. An afterSanitizeElements or afterSanitizeAttributes hook that removes a non-root element (a documented, supported pattern) detaches that element's subtree with no neutralization, so descendant on* handlers remain armed on the caller's live tree after sanitize() returns, yielding DOM XSS. No special privilege is required beyond supplying markup to an application that uses IN_PLACE together with a node-removing afterSanitize hook.
Root Cause
IN_PLACE sanitization mutates the caller's live document, so any element a hook detaches from that tree must have its subtree neutralized before sanitize() returns; otherwise a queued resource-event handler (for example an <img onerror> that began loading when the caller built the tree) fires in page scope even though the handler never reached the sanitized output. The guard helper _handleHookDetachedNode (which calls _neutralizeSubtree in IN_PLACE) is invoked only after beforeSanitizeElements and after uponSanitizeElement. It is never invoked from the afterSanitize return paths, and _sanitizeAttributes never calls it at all. The post-walk IN_PLACE neutralization pass iterates only DOMPurify.removed, and hook-detached nodes are intentionally not recorded there, so that pass cannot reach them either.
src/purify.ts
_sanitizeElements: _handleHookDetachedNode present at lines 2142 and 2175
(before/upon), absent after afterSanitizeElements at lines 2208 and 2249.
_sanitizeAttributes: afterSanitizeAttributes fires at line 2670 with no
detach re-check; the function never calls _handleHookDetachedNode.
Post-walk IN_PLACE pass (lines 3081-3090) iterates DOMPurify.removed only,
which by design (comment at lines 2096-2099) excludes hook-detached nodes.
Impact
An attacker who supplies markup processed by a victim application that runs DOMPurify.sanitize(node, { IN_PLACE: true }) and registers a node-removing afterSanitizeElements or afterSanitizeAttributes hook can retain arbitrary on* event handlers on descendants of a removed non-root element. Because IN_PLACE operates on the caller's live document, a queued resource-event handler on such a descendant fires in the page origin after the synchronous sanitize() call returns, giving script execution in the victim's session (DOM XSS). This defeats DOMPurify's IN_PLACE contract to neutralize handlers on subtrees removed from the live tree. The capability is script execution in the victim origin; exact confidentiality and integrity effects depend on the hosting application's session.
Proof of Concept
Dependencies: Node.js and jsdom. The harness builds a live tree, registers a
removal hook, runs IN_PLACE sanitize(), then inspects whether the attacker
onerror handler survives on the detached descendant. jsdom does not perform
real image loads, so the retained handler (rather than an actual fired event)
is the observed hazard; the retained on* attribute on a live detached node
after a synchronous sanitize() return is the neutralization failure.
Reproduction steps:
- Build a live subtree
#root > section#wrap > img[onerror]where#rootis the walk root andsection#wrapis a non-root wrapper. - Register an
afterSanitizeElements(orafterSanitizeAttributes) hook that callsnode.remove()on the wrapper. - Call
DOMPurify.sanitize(root, { IN_PLACE: true }). - Observe that the descendant
<img>retains itsonerrorhandler, whereas the same removal performed in abeforeSanitizeElements/uponSanitizeElementhook strips it.
const { JSDOM } = require('jsdom');…
- Record
GHSA-p98j-92pf-mc4p- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
5.1
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
CVE-2026-109344- CVSS 4.0
- 5.1 Medium
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
- 6.1 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
5.1
DOMPurify from 3.4.13 before 3.4.16 DOM XSS via IN_PLACE afterSanitize Hooks
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
CVE-2026-109344- CVSS 4.0
- 5.1 Medium
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 - Weakness
- CWE-79
- Updated
- 2026-11-02
Show 6 moreShow less
OSVDEBIAN-CVE-2026-109344
5.1
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
DEBIAN-CVE-2026-109344- CVSS 4.0
- 5.1 Medium
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 - Updated
- 2026-11-02
OSVGHSA-p98j-92pf-mc4p
2.3
DOMPurify: IN_PLACE: node-removing afterSanitize hook leaves detached subtree event handlers armed, causing DOM XSS
Summary
In IN_PLACE mode DOMPurify sanitizes the caller's live DOM subtree directly. To close a known hazard (GHSA-55q2-fjhq-7xh7), the library neutralizes any node a hook detaches during sanitization by stripping its subtree's non-allow-listed attributes, but this neutralization is wired only into the beforeSanitizeElements and uponSanitizeElement hook sites. An afterSanitizeElements or afterSanitizeAttributes hook that removes a non-root element (a documented, supported pattern) detaches that element's subtree with no neutralization, so descendant on* handlers remain armed on the caller's live tree after sanitize() returns, yielding DOM XSS. No special privilege is required beyond supplying markup to an application that uses IN_PLACE together with a node-removing afterSanitize hook.
Root Cause
IN_PLACE sanitization mutates the caller's live document, so any element a hook detaches from that tree must have its subtree neutralized before sanitize() returns; otherwise a queued resource-event handler (for example an <img onerror> that began loading when the caller built the tree) fires in page scope even though the handler never reached the sanitized output. The guard helper _handleHookDetachedNode (which calls _neutralizeSubtree in IN_PLACE) is invoked only after beforeSanitizeElements and after uponSanitizeElement. It is never invoked from the afterSanitize return paths, and _sanitizeAttributes never calls it at all. The post-walk IN_PLACE neutralization pass iterates only DOMPurify.removed, and hook-detached nodes are intentionally not recorded there, so that pass cannot reach them either.
src/purify.ts
_sanitizeElements: _handleHookDetachedNode present at lines 2142 and 2175
(before/upon), absent after afterSanitizeElements at lines 2208 and 2249.
_sanitizeAttributes: afterSanitizeAttributes fires at line 2670 with no
detach re-check; the function never calls _handleHookDetachedNode.
Post-walk IN_PLACE pass (lines 3081-3090) iterates DOMPurify.removed only,
which by design (comment at lines 2096-2099) excludes hook-detached nodes.
Impact
An attacker who supplies markup processed by a victim application that runs DOMPurify.sanitize(node, { IN_PLACE: true }) and registers a node-removing afterSanitizeElements or afterSanitizeAttributes hook can retain arbitrary on* event handlers on descendants of a removed non-root element. Because IN_PLACE operates on the caller's live document, a queued resource-event handler on such a descendant fires in the page origin after the synchronous sanitize() call returns, giving script execution in the victim's session (DOM XSS). This defeats DOMPurify's IN_PLACE contract to neutralize handlers on subtrees removed from the live tree. The capability is script execution in the victim origin; exact confidentiality and integrity effects depend on the hosting application's session.
Proof of Concept
Dependencies: Node.js and jsdom. The harness builds a live tree, registers a
removal hook, runs IN_PLACE sanitize(), then inspects whether the attacker
onerror handler survives on the detached descendant. jsdom does not perform
real image loads, so the retained handler (rather than an actual fired event)
is the observed hazard; the retained on* attribute on a live detached node
after a synchronous sanitize() return is the neutralization failure.
Reproduction steps:
- Build a live subtree
#root > section#wrap > img[onerror]where#rootis the walk root andsection#wrapis a non-root wrapper. - Register an
afterSanitizeElements(orafterSanitizeAttributes) hook that callsnode.remove()on the wrapper. - Call
DOMPurify.sanitize(root, { IN_PLACE: true }). - Observe that the descendant
<img>retains itsonerrorhandler, whereas the same removal performed in abeforeSanitizeElements/uponSanitizeElementhook strips it.
const { JSDOM } = require('jsdom');…
- Record
GHSA-p98j-92pf-mc4p- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-109344
5.1
DOMPurify from 3.4.13 before 3.4.16 contains a cross-site scripting vulnerability because IN_PLACE mode fails to neutralize subtrees detached by afterSanitizeElements or afterSanitizeAttributes hooks. Attackers can supply markup with on* handlers inside a wrapper removed by such hooks, leaving handlers armed on the live DOM to execute script in the victim origin.
- Record
UBUNTU-CVE-2026-109344- CVSS 4.0
- 5.1 Medium
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 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
OSVCLSA-2026-1790884902 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 3.4.14-tuxcare.1.
- Record
CLSA-2026-1790884902- Updated
- 2026-11-02
OSVCLSA-2026-1792508927 No score
TuxCare security update for dompurify (2 CVEs)
TuxCare rebuilt dompurify to address 2 CVEs. Fixed in 3.4.13-tuxcare.1.
- Record
CLSA-2026-1792508927- Updated
- 2026-11-02
OSVCLSA-2026-1792509241 No score
TuxCare security update for dompurify (2 CVEs)
TuxCare rebuilt dompurify to address 2 CVEs. Fixed in 3.4.15-tuxcare.1.
- Record
CLSA-2026-1792509241- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
dompurifynpm |
3.4.13 to 3.4.15 | 3.4.16 |
dompurifyTuxCare:npm |
3.4.14-tuxcare.0 up to 3.4.14-tuxcare.1, plus listed versions 3.4.14 |
3.4.14-tuxcare.1 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
3.4.13-tuxcare.0 up to 3.4.13-tuxcare.1, plus listed versions 3.4.13 |
3.4.13-tuxcare.1 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
3.4.15-tuxcare.0 up to 3.4.15-tuxcare.1, plus listed versions 3.4.15 |
3.4.15-tuxcare.1 |
node-dompurifyDebian:12 |
All versions, plus listed versions 2.4.1+dfsg+~2.4.0-12.4.1+dfsg+~2.4.0-22.4.1+dfsg+~2.4.0-2+deb12u13.0.9+dfsg+~3.0.5-13.1.6+dfsg+~3.0.5-1 Show 13 moreShow less3.1.7+dfsg+~3.0.5-13.1.7+dfsg+~3.0.5-23.2.5+dfsg-13.3.2+dfsg-13.3.3+dfsg-13.3.3+dfsg-23.4.1+dfsg-13.4.12+dfsg-13.4.13+dfsg-13.4.13+dfsg-23.4.16+dfsg-13.4.2+dfsg-13.4.5+dfsg-1 |
None known |
node-dompurifyDebian:13 |
All versions, plus listed versions 3.1.7+dfsg+~3.0.5-23.2.5+dfsg-13.3.2+dfsg-13.3.3+dfsg-13.3.3+dfsg-2 Show 7 moreShow less3.4.1+dfsg-13.4.12+dfsg-13.4.13+dfsg-13.4.13+dfsg-23.4.16+dfsg-13.4.2+dfsg-13.4.5+dfsg-1 |
None known |
node-dompurifyDebian:14 |
before 3.4.16+dfsg-1, plus listed versions 3.1.7+dfsg+~3.0.5-23.2.5+dfsg-13.3.2+dfsg-13.3.3+dfsg-13.3.3+dfsg-2 Show 6 moreShow less3.4.1+dfsg-13.4.12+dfsg-13.4.13+dfsg-13.4.13+dfsg-23.4.2+dfsg-13.4.5+dfsg-1 |
3.4.16+dfsg-1 |
dompurifynpm |
3.4.13 up to 3.4.16 | 3.4.16 |
node-dompurifyUbuntu:22.04:LTS |
All versions, plus listed versions 2.3.0+dfsg-22.3.3+dfsg-1 |
None known |
node-dompurifyUbuntu:24.04:LTS |
All versions, plus listed versions 2.4.1+dfsg+~2.4.0-13.0.9+dfsg+~3.0.5-1 |
None known |
node-dompurifyUbuntu:26.10 |
All versions, plus listed versions 3.3.3+dfsg-13.4.2+dfsg-13.4.5+dfsg-13.4.12+dfsg-13.4.13+dfsg-13.4.13+dfsg-2 |
None known |
node-dompurifyUbuntu:26.04:LTS |
All versions, plus listed versions 3.1.7+dfsg+~3.0.5-23.3.3+dfsg-1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-109344 is a medium vulnerability (CVSS 5.1) in Cure53 Dompurify, published September 30, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.19%, the 8th percentile. Fixed in dompurify 3.4.16.
References
github.com/cure53/DOMPurify/security/advisories/GHSA-p98j-92pf-mc4p
vulncheck.com/advisories/dompurify-from-3.4.13-before-3.4.16-dom-xss-via-in-place-aftersanitize-hooks
nvd.nist.gov/vuln/detail/CVE-2026-109344
github.com/advisories/GHSA-59fh-8xxf-p69h
github.com/cure53/DOMPurify/pull/1636
github.com/cure53/DOMPurify/commit/b9b9d80f7e401771c2ccaef5f45def7eec8f27d7
Show 7 moreShow less
github.com/cure53/DOMPurify/releases/tag/3.4.16
github.com/advisories/GHSA-p98j-92pf-mc4p
github.com/CVEProject/cvelistV5/tree/main/cves/2026/109xxx/CVE-2026-109344.json
security-tracker.debian.org/tracker/CVE-2026-109344
github.com/cure53/DOMPurify
ubuntu.com/security/CVE-2026-109344
cve.org/CVERecord?id=CVE-2026-109344
Left out here: the rest of 2 source descriptions. The JSON record has them.