CVE-2026-49459
DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.1
DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
CVE-2026-49459- 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, CWE-693, CWE-1321
- Updated
- 2026-11-02
EUVDEUVD-2026-44450
6.1
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
EUVD-2026-44450- 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
GitHub AdvisoriesGHSA-2cf5-4w76-r9qv
7.3
Arbitrary Code Execution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.2 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).
The following template can be used to demonstrate the vulnerability:
{{#with split as |a|}}
{{pop (push "alert('Vulnerable Handlebars JS');")}}
{{#with (concat (lookup join (slice 0 1)))}}
{{#each (slice 2 3)}}
{{#with (apply 0 a)}}
{{.}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}```
## Recommendation
Upgrade to version 3.0.8, 4.5.2 or later.
- Record
GHSA-2cf5-4w76-r9qv- CVSS 3.1
- 7.3 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:H/A:L - Weakness
- CWE-94
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-76mc-f452-cxcm
6.1
DOMPurify: Hook mutation of `data.allowedTags` / `data.allowedAttributes` permanently pollutes `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR`
Hook mutation of data.allowedTags / data.allowedAttributes permanently pollutes DEFAULT_ALLOWED_TAGS / DEFAULT_ALLOWED_ATTR
CWE: CWE-501 (Trust Boundary Violation — hook-scoped mutation leaks to global default sets) via CWE-693 (Protection Mechanism Failure — the default allow-list is silently widened for all subsequent sanitize calls)
Summary
The data.allowedTags and data.allowedAttributes fields passed to uponSanitizeElement and uponSanitizeAttribute hooks are direct references to the library's live ALLOWED_TAGS / ALLOWED_ATTR sets. For sanitize calls that don't supply an explicit cfg.ALLOWED_TAGS / cfg.ALLOWED_ATTR array, those live sets are themselves direct references to the module-level DEFAULT_ALLOWED_TAGS / DEFAULT_ALLOWED_ATTR constants. A hook that mutates these fields — a natural-looking pattern for "allow X for this iteration" — permanently writes new entries into the default constants for the DOMPurify instance's lifetime. Every subsequent sanitize call that doesn't override the config inherits the widened defaults, so an attacker payload that uses the poisoned tag/attribute name survives sanitization. removeAllHooks(), clearConfig(), and even passing a fresh cfg: {} do not recover; only constructing a new DOMPurify instance does.
The maintainer's existing defense at src/purify.ts:696-700 explicitly clones DEFAULT_ALLOWED_TAGS before mutating it via cfg.ADD_TAGS (array form), demonstrating awareness of this exact class. The hook path remained uncovered.
Affected
- DOMPurify ≤ 3.4.5, including
mainat7996f1dc78eb8b7922388aed75d94a9f8fad9a36 - Any application that installs a hook on
uponSanitizeElementoruponSanitizeAttributethat writes todata.allowedTags[...] = trueordata.allowedAttributes[...] = trueand later sanitizes attacker-influenced content with default config (no explicitcfg.ALLOWED_TAGS/cfg.ALLOWED_ATTRarray)
Vulnerability details
[A] — data.allowedTags is a reference to ALLOWED_TAGS
src/purify.ts:1206-1209:
_executeHooks(hooks.uponSanitizeElement, currentNode, {
tagName,
allowedTags: ALLOWED_TAGS, // [A] direct reference; hook mutation
// mutates the very ALLOWED_TAGS the
// library checks on the next element
});
src/purify.ts:1494-1500 (the matching attribute hook):
const hookEvent = {
attrName: '',
attrValue: '',
keepAttr: true,
allowedAttributes: ALLOWED_ATTR, // [A'] same pattern
forceKeepAttr: undefined,
};
[B] — ALLOWED_TAGS = DEFAULT_ALLOWED_TAGS for default-cfg sanitize calls
src/purify.ts:527-531:
ALLOWED_TAGS =
objectHasOwnProperty(cfg, 'ALLOWED_TAGS') &&
arrayIsArray(cfg.ALLOWED_TAGS)
? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)
: DEFAULT_ALLOWED_TAGS; // [B] reference assignment; ALLOWED_TAGS
// IS the DEFAULT_ALLOWED_TAGS object
(The ALLOWED_ATTR = DEFAULT_ALLOWED_ATTR path at :532-536 is symmetric.)
The mismatch
A hook author who writes data.allowedTags['script'] = true reasonably expects per-call scope — the API name is "data", suggesting per-event payload. But [A] makes this a direct reference, and [B] makes that reference equal to the module-level default for the common default-cfg path. The hook's mutation therefore writes to a constant that every subsequent default-cfg sanitize call rebinds to.
The maintainer already recognized this class for the ADD_TAGS array path — src/purify.ts:696-700:
} else if (arrayIsArray(cfg.ADD_TAGS)) {
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
ALLOWED_TAGS = clone(ALLOWED_TAGS); // explicitly clone DEFAULT before
// mutating to avoid this pollution
}
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
}
The same…
- Record
GHSA-76mc-f452-cxcm- 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-501, CWE-693
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-cmwh-pvxp-8882
5.1
DOMPurify: Permanent `ALLOWED_ATTR` pollution via `setConfig()` bypassing the hook clone-guard (incomplete fix of the 3.4.7 hook-pollution patch)
Summary
DOMPurify 3.4.7 shipped a security fix ("permanent hook pollution") that makes a registered uponSanitizeAttribute hook's mutation of data.allowedAttributes non-persistent — so allowing an attribute for one element does not leak into later sanitize() calls. The fix clones ALLOWED_ATTR inside _parseConfig.
That guard is silently bypassed whenever the application uses the persistent-config API DOMPurify.setConfig(). setConfig() sets the module flag SET_CONFIG = true, which causes sanitize() to skip _parseConfig entirely — and the clone-guard lives inside _parseConfig. The hook is then handed the live, shared ALLOWED_ATTR object; any data.allowedAttributes[name] = true it writes mutates that shared object permanently, for the lifetime of the DOMPurify instance, across every subsequent call, and across all elements.
If an application uses setConfig() together with an uponSanitizeAttribute hook that conditionally allows a dangerous attribute (onerror, onclick, onmouseover, srcdoc, formaction, …) for "trusted" elements, then one trusted render permanently allows that attribute on untrusted, attacker-controlled content — yielding stored XSS in viewers' browsers. DOMPurify applies no separate /^on/ event-handler blocklist: attribute stripping is governed entirely by the allowlist, so a polluted allowlist is the only gate, and survival in the output is final.
Affected configuration (preconditions)
The vulnerability is triggered when an application does both:
- Calls
DOMPurify.setConfig(...)once (the recommended pattern for a fixed, persistent policy), and - Registers an
uponSanitizeAttributehook that writesdata.allowedAttributes[name] = trueto conditionally allow an attribute (e.g. only for elements bearing a trust marker).
This hook pattern is demonstrated in DOMPurify's own test suite, and the per-call variant of exactly this leak is what 3.4.7 was released to fix.
Root cause (source: src/purify.ts, v3.4.10)
The 3.4.7 clone-guard — only inside _parseConfig:
// src/purify.ts _parseConfig() (lines ~950-968)
// "if a hook is registered AND the set still points at the default constant, clone it.
// The hook then mutates the clone ... and the next default-cfg call rebinds to the untouched original."
if ( ... && hooks.uponSanitizeAttribute.length > 0) {
ALLOWED_TAGS = clone(ALLOWED_TAGS); // line 961
}
if ( ... hooks.uponSanitizeAttribute.length > 0 ... ) {
ALLOWED_ATTR = clone(ALLOWED_ATTR); // line 968
}
sanitize() skips _parseConfig on the persistent-config path:
// src/purify.ts DOMPurify.sanitize() (line 2369)
if (!SET_CONFIG) {
_parseConfig(cfg); // <-- clone-guard lives in here; SKIPPED when SET_CONFIG is true
}
setConfig() sets the flag that disables the guard:
// src/purify.ts (lines 2596-2598)
DOMPurify.setConfig = function (cfg = {}) {
_parseConfig(cfg);
SET_CONFIG = true; // every later sanitize() now skips _parseConfig
};
The hook is handed the live allowlist binding, and there is no secondary event-handler defense:
// src/purify.ts (line 2088) — hook event exposes the shared object by reference
allowedAttributes: ALLOWED_ATTR,
// (line 2108) hooks.uponSanitizeAttribute executed; a write to data.allowedAttributes mutates ALLOWED_ATTR itself
// _isValidAttribute gates purely on ALLOWED_ATTR[lcName]; DOMPurify uses NO /^on/ blocklist by design.
Net: after setConfig(), the clone-guard never runs, so the hook's allowedAttributes mutation is a permanent write to the instance's shared ALLOWED_ATTR.
Proof of Concept
Environment: npm i [email protected] jsdom (Node; identical mechanism to isomorphic-dompurify, and to a browser instance).
PoC 1 — the leak (trusted render permanently allows onerror on attacker content)
const createDOMPurify =…
- Record
GHSA-cmwh-pvxp-8882- CVSS 4.0
- 5.1 Medium
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 - Weakness
- CWE-79, CWE-471, CWE-665
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-gvmj-g25r-r7wr
2.0
DOMPurify: SAFE_FOR_TEMPLATES bypass - template expressions survive sanitization inside <template> content when using DOM output modes
Summary
When DOMPurify is configured with both SAFE_FOR_TEMPLATES: true and RETURN_DOM: true (or IN_PLACE: true), an attacker can inject template expressions, such as ${evil}, {{evil}}, or <%evil%>, that survive the sanitization pass inside <template> element content. This bypasses the explicit purpose of SAFE_FOR_TEMPLATES, which is to prevent template engine evaluation of user-supplied content.
Note: The string output path is not affected. Only the DOM return paths (
RETURN_DOM: true,RETURN_DOM_FRAGMENT: true,IN_PLACE: true) are vulnerable.
Description
Background
SAFE_FOR_TEMPLATES is designed to strip {{ }}, ${ }, and <% %> expressions from sanitized output so that downstream template engines do not evaluate user-controlled content. The feature operates through two mechanisms:
- Per-node scrubbing (
_sanitizeElements,src/purify.ts:1403), scrubs individual text nodes during the main sanitization walk. - Final normalization pass (
_scrubTemplateExpressions,src/purify.ts:1115), callsnode.normalize()to merge adjacent text nodes, then walks the merged nodes and strips any expressions that only appeared after merging.
The Gap
_scrubTemplateExpressions uses a standard NodeIterator rooted at the output body:
// src/purify.ts:1117
const walker = createNodeIterator.call(
node.ownerDocument || node,
node,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | ...,
null
);
Per the DOM specification, a NodeIterator does not descend into <template>.content. The template element's content is a separate DocumentFragment that lives outside the normal child-node tree. For the same reason, node.normalize() (called on line 1116) also does not normalize text nodes inside <template>.content.
This means the final normalization and scrub pass, the only pass that catches expressions formed by merging split text nodes, never runs on <template> content.
How Split Text Nodes Are Created
When DOMPurify removes a disallowed element with KEEP_CONTENT: true (the default), it moves the element's text children into the parent node. This is the standard code path at src/purify.ts:1361–1373:
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
const parentNode = getParentNode(currentNode);
const childNodes = getChildNodes(currentNode);
if (childNodes && parentNode) {
for (let i = childCount - 1; i >= 0; --i) {
const childClone = cloneNode(childNodes[i], true);
parentNode.insertBefore(childClone, getNextSibling(currentNode));
}
}
}
If the removed elements were adjacent siblings inside <template> content, their extracted text nodes end up as adjacent text nodes in the template content fragment. Each individual text node is scrubbed by _sanitizeElements, but since $ and {evil} do not match any expression regex on their own, neither is modified.
The code comment at src/purify.ts:1100 explicitly acknowledges the threat class:
"which only form after text-node normalization (e.g. fragments split across stripped elements) cannot survive into a template-evaluating framework."
The implementation guards against this on the main body, but the guard is not applied to <template> content.
Proof of Concept
Why the Split Works
The bypass relies on splitting ${...} across two adjacent custom elements so that neither fragment matches any DOMPurify regex on its own:
| Fragment | Against TMPLIT_EXPR /\${[\w\W]*/g | Against MUSTACHE_EXPR /{{[\w\W]*\|^[\w\W]*}}/g | Result |
|---|---|---|---|
| $ | Requires ${ - no { follows | No {{ or }} | Survives |
| {alert(document.domain)} | Requires leading $ - absent | No {{, ends with single } not }} | Survives |
| ${alert(document.domain)} | Full match - would be stripped | - | Stripped if seen whole |
DOMPurify only sees each fragment in isolation. It never merges them before…
- Record
GHSA-gvmj-g25r-r7wr- CVSS 4.0
- 2.0 Low
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:P - Weakness
- CWE-79
- Updated
- 2026-11-02
Show 57 moreShow less
GitHub AdvisoriesGHSA-q42p-pg8m-cqh6
7.3
Prototype Pollution in handlebars
Versions of handlebars prior to 4.0.14 are vulnerable to Prototype Pollution. Templates may alter an Objects' prototype, thus allowing an attacker to execute arbitrary code on the server.
Recommendation
For handlebars 4.1.x upgrade to 4.1.2 or later. For handlebars 4.0.x upgrade to 4.0.14 or later.
- Record
GHSA-q42p-pg8m-cqh6- CVSS 3.1
- 7.3 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-471
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-r47g-fvhr-h676
6.1
DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
CWE: CWE-79 (XSS — Improper Neutralization of Input During Web Page Generation) via CWE-693 (Protection Mechanism Failure — silent no-op when _forceRemove is called on a parent-less node)
Summary
When DOMPurify.sanitize(root, { IN_PLACE: true }) is called and root is a <form> whose own attributes carry an event handler (onmouseover, onfocus, onclick, etc.), a single descendant element with a name= attribute matching any of the property names _isClobbered checks (nodeName, setAttribute, namespaceURI, insertBefore, hasChildNodes, childNodes) is sufficient to bypass attribute sanitization on the root. _forceRemove silently no-ops because the root has no parent; the iterator drives on to _sanitizeAttributes, which early-returns on clobbered nodes — and the event handler attribute is never inspected. The sanitized return is the same root, with the handler live.
This affects current main at 89da34e (the just-landed DOM-clobbering hardening fix at 89da34e addressed _sanitizeAttachedShadowRoots walk traversal, not the main _sanitizeElements / _sanitizeAttributes pipeline against the iterator-root node).
Affected
- DOMPurify ≤ 3.4.5, including
mainat89da34e03ec17868e561f87f3747a9371b61a9e7 - Any caller that does
DOMPurify.sanitize(node, { IN_PLACE: true })wherenodeis built from untrusted HTML (e.g., parsed viacreateElement('template').innerHTML = dirtythentemplate.content.firstElementChildhanded in)
Not affected:
- String-input
DOMPurify.sanitize(dirtyString)— the library builds the DOM itself inside_initDocument, the root is the cleanly-created document body, and clobber-named children of the body cannot shadowbodynamed properties (HTMLBodyElement does not carry[LegacyOverrideBuiltIns]) - IN_PLACE where the root is not an HTMLFormElement
- IN_PLACE where the attacker cannot place a clobber-named child inside the root
Vulnerability details
Code paths
[A] — _forceRemove at src/purify.ts:930-939:
const _forceRemove = function (node: Node): void {
arrayPush(DOMPurify.removed, { element: node });
try {
// eslint-disable-next-line unicorn/prefer-dom-node-remove
getParentNode(node).removeChild(node); // [A1] throws when getParentNode returns null
} catch (_) {
remove(node); // [A2] WebIDL Node.remove() — spec-defined no-op
} // when the node has no parent
};
When the iterator-root has no parent (the standard IN_PLACE case where the caller hands in a detached node), getParentNode(node) returns null, null.removeChild(node) throws, the catch falls to remove(node) — which per WebIDL is Element.prototype.remove.call(node), and per spec does nothing if the node has no parent. Nothing about _forceRemove's contract acknowledges this — the function appears to its callers as "the node is gone now," but the node is still in place.
[B] — _sanitizeAttributes at src/purify.ts:1490-1492:
const _sanitizeAttributes = function (currentNode: Element): void {
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
const { attributes } = currentNode;
/* Check if we have attributes; if not we might have a text node */
if (!attributes || _isClobbered(currentNode)) {
return; // [B] silently skips ALL attribute checks
} // for clobbered nodes
...
};
The skip at [B] is deliberate — the intent is to avoid touching nodes the library has already decided to discard. The invariant the comment implies is "if _isClobbered, then _sanitizeElements already removed this node, so we will never reach _sanitizeAttributes on it." That invariant holds for every non-root node…
- Record
GHSA-r47g-fvhr-h676- 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, CWE-693, CWE-1321
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-vxr8-fq34-vvx9
2.1
DOMPurify: Trusted Types policy survives `clearConfig()` and can poison later `RETURN_TRUSTED_TYPE` output
Impact
A DOMPurify instance that is reused across trust boundaries can stay bound to a previously supplied TRUSTED_TYPES_POLICY even after clearConfig() is called. A later caller that requests RETURN_TRUSTED_TYPE receives a TrustedHTML object created by the old policy, not by a clean default configuration.
If the old policy is unsafe or controlled by a less-trusted integration, this turns a later "default" sanitize call into script execution at a Trusted Types sink. TRUSTED_TYPES_POLICY: null on the later call also does not clear the retained policy.
dompurify-trusted-types-policy-survives-clearconfig-poc.js
Affected version
Tested against DOMPurify 3.4.8, repository commit 825e617753ac1169306a542d3174a77f717a0cf6.
Root cause
_parseConfig() overwrites trustedTypesPolicy when cfg.TRUSTED_TYPES_POLICY is truthy, but the default/null path only initializes the internal policy when trustedTypesPolicy === undefined. Once a custom policy has been set, later default config parsing leaves it in place.
Relevant code:
src/purify.ts:786-812accepts and storescfg.TRUSTED_TYPES_POLICY.src/purify.ts:813-832does not reset an existing policy when config has no policy or hasTRUSTED_TYPES_POLICY: null.src/purify.ts:2123-2125signs the final serialized HTML with the retained policy whenRETURN_TRUSTED_TYPEis true.src/purify.ts:2133-2136clearConfig()only clearsCONFIGandSET_CONFIG; it does not resettrustedTypesPolicyoremptyHTML.
Local PoC
Run from the DOMPurify checkout, or set DOMPURIFY_REPO:
node /home/dompurify-trusted-types-policy-survives-clearconfig-poc.js
Observed output:
{
"result": {
"baseline": "<b>baseline</b>",
"duringPolicy": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"afterClearString": "<img src=\"x\">",
"afterClearTrustedType": "[object TrustedHTML]",
"afterClearTrusted": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"afterNullTrusted": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"mountedHTML": "<img src=\"x\" onerror=\"alert("TT_POLICY_SURVIVED_CLEARCONFIG")\">"
},
"dialogs": [
"TT_POLICY_SURVIVED_CLEARCONFIG"
]
}
The important part is the split behavior after cleanup:
purify.clearConfig(); purify.sanitize(...);returns a normal sanitized string (<img src="x">), because the later call is not asking for a Trusted Type.purify.clearConfig(); purify.sanitize(..., { RETURN_TRUSTED_TYPE: true });still uses the old policy and returns attacker-controlledTrustedHTML.- Passing
{ TRUSTED_TYPES_POLICY: null, RETURN_TRUSTED_TYPE: true }also still returns attacker-controlledTrustedHTML.
Preconditions
This is a shared-instance state contamination issue. It matters when one DOMPurify instance is reused by multiple integrations, plugins, request handlers, or components with different trust levels, and a cleanup step relies on clearConfig() to restore safe defaults.
This is not a default string-input bypass. An attacker must be able to influence a prior TRUSTED_TYPES_POLICY on the reused instance, or a less-trusted integration must have installed an unsafe policy.
Severity
impact is XSS at a Trusted Types sink in applications that reuse a DOMPurify instance across trust boundaries. Attack complexity is high because exploitation depends on prior policy injection or a less-trusted integration and a later RETURN_TRUSTED_TYPE sink.
Suggested fix
Make clearConfig() reset Trusted Types state as part of restoring defaults, or have _parseConfig() explicitly clear trustedTypesPolicy and emptyHTML when TRUSTED_TYPES_POLICY: null is supplied.
- Record
GHSA-vxr8-fq34-vvx9- CVSS 4.0
- 2.1 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-693
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-g9r4-xpmj-mj65 No score
Prototype Pollution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to prototype pollution. It is possible to add or modify properties to the Object prototype through a malicious template. This may allow attackers to crash the application or execute Arbitrary Code in specific conditions.
Recommendation
Upgrade to version 3.0.8, 4.5.3 or later.
- Record
GHSA-g9r4-xpmj-mj65- Weakness
- CWE-1321
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-q2c6-c6pm-g3gh No score
Arbitrary Code Execution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It is due to an incomplete fix for a previous issue. This vulnerability can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).
Recommendation
Upgrade to version 3.0.8, 4.5.3 or later.
- Record
GHSA-q2c6-c6pm-g3gh- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-x4vx-rjvf-j5p4 No score
DOMPurify: `IN_PLACE` mode trusts attacker-controlled `nodeName` on live non-form nodes, allowing script retention and XSS via attacker-supplied DOM objects
Summary
When DOMPurify.sanitize(root, { IN_PLACE: true }) is called on an attacker-supplied live DOM node, DOMPurify still trusts currentNode.nodeName for non-form nodes in the main _sanitizeElements pipeline. A real <script> child node whose observable nodeName is attacker-controlled can therefore be misclassified as an allowed element and retained. When the sanitized tree is inserted into a live document, the script executes.
This affects current 3.4.6. The recent IN_PLACE hardening work covers clobbered form handling and foreign-realm shadow/template traversal, but does not harden the main per-node element decision for hostile non-form live nodes.
Affected
- DOMPurify
3.4.6 - Any caller that does
DOMPurify.sanitize(node, { IN_PLACE: true })on attacker-supplied live DOM nodes - Verified attacker-controlled node sources:
- same-origin
iframe→ live node passed by reference - same-origin
window.open()popup → live node passed by reference - same-origin foreign node adopted into the host document via
document.adoptNode(node)and then sanitized in-place
- same-origin
Not affected:
- String-input
DOMPurify.sanitize(dirtyString)
Vulnerability details
Code paths
[A] — _sanitizeElements uses the instance-visible nodeName for the allow/forbid decision:
const _sanitizeElements = function (currentNode: any): boolean {
...
if (_isClobbered(currentNode)) {
_forceRemove(currentNode);
return true;
}
const tagName = transformCaseFunc(currentNode.nodeName);
...
if (
FORBID_TAGS[tagName] ||
(!(...) && !ALLOWED_TAGS[tagName])
) {
...
_forceRemove(currentNode);
return true;
}
...
};
For non-form nodes, _isClobbered(currentNode) returns false early. The subsequent element decision therefore trusts currentNode.nodeName directly.
[B] — _isClobbered is form-specific:
const _isClobbered = function (element: Element): boolean {
const realTagName = getNodeName ? getNodeName(element) : null;
if (typeof realTagName !== 'string') {
return false;
}
if (transformCaseFunc(realTagName) !== 'form') {
return false;
}
return (...);
};
The hardening is intentionally scoped to form. Non-form nodes are not checked for divergence between the instance-visible property view and the trusted prototype getter view.
Why the bypass works
The attack does not depend on string HTML parsing. It depends on a hostile live DOM object crossing a trust boundary into DOMPurify's IN_PLACE pipeline.
If the attacker controls a same-origin subcontext (iframe or popup), they can prepare a real DOM subtree there and then pass the live node object by reference to a host page that trusts DOMPurify.sanitize(node, { IN_PLACE: true }) as its final sanitization step.
For the verified primitive below:
- the real child node is
<script> - its script text is attacker-controlled
- the observable
nodeNameis attacker-controlled and made to appear as"DIV" _sanitizeElementstherefore classifies the real<script>child as an allowed element- the real
<script>survives in the sanitized tree and executes on insertion
This primitive survives:
- direct reference passing
document.adoptNode(node)followed byIN_PLACE
It does not survive:
importNodecloneNode
because those paths materialize a fresh node and discard the hostile object semantics.
Proof of concept
(1) Minimal — runnable in a single browser context
<!doctype html>
<html><body>
<script src="dist/purify.js"></script>
<script>
const foreign = window.open('about:blank', '_blank', 'noopener=no');
const host = foreign.document.createElement('div');
const script = foreign.document.createElement('script');
script.textContent = 'window.__pwned = 1';
Object.defineProperty(script, 'nodeName', {
value: 'DIV',
configurable: true,
});
host.appendChild(script);
DOMPurify.sanitize(host, {…
- Record
GHSA-x4vx-rjvf-j5p4- Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
6.1
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
CVE-2026-49459- 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, CWE-693, CWE-1321
- Updated
- 2026-11-02
OSV
6.1
DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
CVE-2026-49459- 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-1321, CWE-693, CWE-79
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-49459
6.1
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
DEBIAN-CVE-2026-49459- 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
OSVGHSA-2cf5-4w76-r9qv
7.3
Arbitrary Code Execution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.2 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).
The following template can be used to demonstrate the vulnerability:
{{#with split as |a|}}
{{pop (push "alert('Vulnerable Handlebars JS');")}}
{{#with (concat (lookup join (slice 0 1)))}}
{{#each (slice 2 3)}}
{{#with (apply 0 a)}}
{{.}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}```
## Recommendation
Upgrade to version 3.0.8, 4.5.2 or later.
- Record
GHSA-2cf5-4w76-r9qv- CVSS 3.1
- 7.3 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:H/A:L - Weakness
- CWE-94
- Updated
- 2026-11-02
OSVGHSA-76mc-f452-cxcm
6.1
DOMPurify: Hook mutation of `data.allowedTags` / `data.allowedAttributes` permanently pollutes `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR`
Hook mutation of data.allowedTags / data.allowedAttributes permanently pollutes DEFAULT_ALLOWED_TAGS / DEFAULT_ALLOWED_ATTR
CWE: CWE-501 (Trust Boundary Violation — hook-scoped mutation leaks to global default sets) via CWE-693 (Protection Mechanism Failure — the default allow-list is silently widened for all subsequent sanitize calls)
Summary
The data.allowedTags and data.allowedAttributes fields passed to uponSanitizeElement and uponSanitizeAttribute hooks are direct references to the library's live ALLOWED_TAGS / ALLOWED_ATTR sets. For sanitize calls that don't supply an explicit cfg.ALLOWED_TAGS / cfg.ALLOWED_ATTR array, those live sets are themselves direct references to the module-level DEFAULT_ALLOWED_TAGS / DEFAULT_ALLOWED_ATTR constants. A hook that mutates these fields — a natural-looking pattern for "allow X for this iteration" — permanently writes new entries into the default constants for the DOMPurify instance's lifetime. Every subsequent sanitize call that doesn't override the config inherits the widened defaults, so an attacker payload that uses the poisoned tag/attribute name survives sanitization. removeAllHooks(), clearConfig(), and even passing a fresh cfg: {} do not recover; only constructing a new DOMPurify instance does.
The maintainer's existing defense at src/purify.ts:696-700 explicitly clones DEFAULT_ALLOWED_TAGS before mutating it via cfg.ADD_TAGS (array form), demonstrating awareness of this exact class. The hook path remained uncovered.
Affected
- DOMPurify ≤ 3.4.5, including
mainat7996f1dc78eb8b7922388aed75d94a9f8fad9a36 - Any application that installs a hook on
uponSanitizeElementoruponSanitizeAttributethat writes todata.allowedTags[...] = trueordata.allowedAttributes[...] = trueand later sanitizes attacker-influenced content with default config (no explicitcfg.ALLOWED_TAGS/cfg.ALLOWED_ATTRarray)
Vulnerability details
[A] — data.allowedTags is a reference to ALLOWED_TAGS
src/purify.ts:1206-1209:
_executeHooks(hooks.uponSanitizeElement, currentNode, {
tagName,
allowedTags: ALLOWED_TAGS, // [A] direct reference; hook mutation
// mutates the very ALLOWED_TAGS the
// library checks on the next element
});
src/purify.ts:1494-1500 (the matching attribute hook):
const hookEvent = {
attrName: '',
attrValue: '',
keepAttr: true,
allowedAttributes: ALLOWED_ATTR, // [A'] same pattern
forceKeepAttr: undefined,
};
[B] — ALLOWED_TAGS = DEFAULT_ALLOWED_TAGS for default-cfg sanitize calls
src/purify.ts:527-531:
ALLOWED_TAGS =
objectHasOwnProperty(cfg, 'ALLOWED_TAGS') &&
arrayIsArray(cfg.ALLOWED_TAGS)
? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)
: DEFAULT_ALLOWED_TAGS; // [B] reference assignment; ALLOWED_TAGS
// IS the DEFAULT_ALLOWED_TAGS object
(The ALLOWED_ATTR = DEFAULT_ALLOWED_ATTR path at :532-536 is symmetric.)
The mismatch
A hook author who writes data.allowedTags['script'] = true reasonably expects per-call scope — the API name is "data", suggesting per-event payload. But [A] makes this a direct reference, and [B] makes that reference equal to the module-level default for the common default-cfg path. The hook's mutation therefore writes to a constant that every subsequent default-cfg sanitize call rebinds to.
The maintainer already recognized this class for the ADD_TAGS array path — src/purify.ts:696-700:
} else if (arrayIsArray(cfg.ADD_TAGS)) {
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
ALLOWED_TAGS = clone(ALLOWED_TAGS); // explicitly clone DEFAULT before
// mutating to avoid this pollution
}
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
}
The same…
- Record
GHSA-76mc-f452-cxcm- 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-501, CWE-693
- Updated
- 2026-11-02
OSVGHSA-cmwh-pvxp-8882
5.1
DOMPurify: Permanent `ALLOWED_ATTR` pollution via `setConfig()` bypassing the hook clone-guard (incomplete fix of the 3.4.7 hook-pollution patch)
Summary
DOMPurify 3.4.7 shipped a security fix ("permanent hook pollution") that makes a registered uponSanitizeAttribute hook's mutation of data.allowedAttributes non-persistent — so allowing an attribute for one element does not leak into later sanitize() calls. The fix clones ALLOWED_ATTR inside _parseConfig.
That guard is silently bypassed whenever the application uses the persistent-config API DOMPurify.setConfig(). setConfig() sets the module flag SET_CONFIG = true, which causes sanitize() to skip _parseConfig entirely — and the clone-guard lives inside _parseConfig. The hook is then handed the live, shared ALLOWED_ATTR object; any data.allowedAttributes[name] = true it writes mutates that shared object permanently, for the lifetime of the DOMPurify instance, across every subsequent call, and across all elements.
If an application uses setConfig() together with an uponSanitizeAttribute hook that conditionally allows a dangerous attribute (onerror, onclick, onmouseover, srcdoc, formaction, …) for "trusted" elements, then one trusted render permanently allows that attribute on untrusted, attacker-controlled content — yielding stored XSS in viewers' browsers. DOMPurify applies no separate /^on/ event-handler blocklist: attribute stripping is governed entirely by the allowlist, so a polluted allowlist is the only gate, and survival in the output is final.
Affected configuration (preconditions)
The vulnerability is triggered when an application does both:
- Calls
DOMPurify.setConfig(...)once (the recommended pattern for a fixed, persistent policy), and - Registers an
uponSanitizeAttributehook that writesdata.allowedAttributes[name] = trueto conditionally allow an attribute (e.g. only for elements bearing a trust marker).
This hook pattern is demonstrated in DOMPurify's own test suite, and the per-call variant of exactly this leak is what 3.4.7 was released to fix.
Root cause (source: src/purify.ts, v3.4.10)
The 3.4.7 clone-guard — only inside _parseConfig:
// src/purify.ts _parseConfig() (lines ~950-968)
// "if a hook is registered AND the set still points at the default constant, clone it.
// The hook then mutates the clone ... and the next default-cfg call rebinds to the untouched original."
if ( ... && hooks.uponSanitizeAttribute.length > 0) {
ALLOWED_TAGS = clone(ALLOWED_TAGS); // line 961
}
if ( ... hooks.uponSanitizeAttribute.length > 0 ... ) {
ALLOWED_ATTR = clone(ALLOWED_ATTR); // line 968
}
sanitize() skips _parseConfig on the persistent-config path:
// src/purify.ts DOMPurify.sanitize() (line 2369)
if (!SET_CONFIG) {
_parseConfig(cfg); // <-- clone-guard lives in here; SKIPPED when SET_CONFIG is true
}
setConfig() sets the flag that disables the guard:
// src/purify.ts (lines 2596-2598)
DOMPurify.setConfig = function (cfg = {}) {
_parseConfig(cfg);
SET_CONFIG = true; // every later sanitize() now skips _parseConfig
};
The hook is handed the live allowlist binding, and there is no secondary event-handler defense:
// src/purify.ts (line 2088) — hook event exposes the shared object by reference
allowedAttributes: ALLOWED_ATTR,
// (line 2108) hooks.uponSanitizeAttribute executed; a write to data.allowedAttributes mutates ALLOWED_ATTR itself
// _isValidAttribute gates purely on ALLOWED_ATTR[lcName]; DOMPurify uses NO /^on/ blocklist by design.
Net: after setConfig(), the clone-guard never runs, so the hook's allowedAttributes mutation is a permanent write to the instance's shared ALLOWED_ATTR.
Proof of Concept
Environment: npm i [email protected] jsdom (Node; identical mechanism to isomorphic-dompurify, and to a browser instance).
PoC 1 — the leak (trusted render permanently allows onerror on attacker content)
const createDOMPurify =…
- Record
GHSA-cmwh-pvxp-8882- CVSS 4.0
- 5.1 Medium
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 - Weakness
- CWE-471, CWE-665, CWE-79
- Updated
- 2026-11-02
OSVGHSA-gvmj-g25r-r7wr
2.0
DOMPurify: SAFE_FOR_TEMPLATES bypass - template expressions survive sanitization inside <template> content when using DOM output modes
Summary
When DOMPurify is configured with both SAFE_FOR_TEMPLATES: true and RETURN_DOM: true (or IN_PLACE: true), an attacker can inject template expressions, such as ${evil}, {{evil}}, or <%evil%>, that survive the sanitization pass inside <template> element content. This bypasses the explicit purpose of SAFE_FOR_TEMPLATES, which is to prevent template engine evaluation of user-supplied content.
Note: The string output path is not affected. Only the DOM return paths (
RETURN_DOM: true,RETURN_DOM_FRAGMENT: true,IN_PLACE: true) are vulnerable.
Description
Background
SAFE_FOR_TEMPLATES is designed to strip {{ }}, ${ }, and <% %> expressions from sanitized output so that downstream template engines do not evaluate user-controlled content. The feature operates through two mechanisms:
- Per-node scrubbing (
_sanitizeElements,src/purify.ts:1403), scrubs individual text nodes during the main sanitization walk. - Final normalization pass (
_scrubTemplateExpressions,src/purify.ts:1115), callsnode.normalize()to merge adjacent text nodes, then walks the merged nodes and strips any expressions that only appeared after merging.
The Gap
_scrubTemplateExpressions uses a standard NodeIterator rooted at the output body:
// src/purify.ts:1117
const walker = createNodeIterator.call(
node.ownerDocument || node,
node,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | ...,
null
);
Per the DOM specification, a NodeIterator does not descend into <template>.content. The template element's content is a separate DocumentFragment that lives outside the normal child-node tree. For the same reason, node.normalize() (called on line 1116) also does not normalize text nodes inside <template>.content.
This means the final normalization and scrub pass, the only pass that catches expressions formed by merging split text nodes, never runs on <template> content.
How Split Text Nodes Are Created
When DOMPurify removes a disallowed element with KEEP_CONTENT: true (the default), it moves the element's text children into the parent node. This is the standard code path at src/purify.ts:1361–1373:
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
const parentNode = getParentNode(currentNode);
const childNodes = getChildNodes(currentNode);
if (childNodes && parentNode) {
for (let i = childCount - 1; i >= 0; --i) {
const childClone = cloneNode(childNodes[i], true);
parentNode.insertBefore(childClone, getNextSibling(currentNode));
}
}
}
If the removed elements were adjacent siblings inside <template> content, their extracted text nodes end up as adjacent text nodes in the template content fragment. Each individual text node is scrubbed by _sanitizeElements, but since $ and {evil} do not match any expression regex on their own, neither is modified.
The code comment at src/purify.ts:1100 explicitly acknowledges the threat class:
"which only form after text-node normalization (e.g. fragments split across stripped elements) cannot survive into a template-evaluating framework."
The implementation guards against this on the main body, but the guard is not applied to <template> content.
Proof of Concept
Why the Split Works
The bypass relies on splitting ${...} across two adjacent custom elements so that neither fragment matches any DOMPurify regex on its own:
| Fragment | Against TMPLIT_EXPR /\${[\w\W]*/g | Against MUSTACHE_EXPR /{{[\w\W]*\|^[\w\W]*}}/g | Result |
|---|---|---|---|
| $ | Requires ${ - no { follows | No {{ or }} | Survives |
| {alert(document.domain)} | Requires leading $ - absent | No {{, ends with single } not }} | Survives |
| ${alert(document.domain)} | Full match - would be stripped | - | Stripped if seen whole |
DOMPurify only sees each fragment in isolation. It never merges them before…
- Record
GHSA-gvmj-g25r-r7wr- CVSS 4.0
- 2.0 Low
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:P - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-q42p-pg8m-cqh6
7.3
Prototype Pollution in handlebars
Versions of handlebars prior to 4.0.14 are vulnerable to Prototype Pollution. Templates may alter an Objects' prototype, thus allowing an attacker to execute arbitrary code on the server.
Recommendation
For handlebars 4.1.x upgrade to 4.1.2 or later. For handlebars 4.0.x upgrade to 4.0.14 or later.
- Record
GHSA-q42p-pg8m-cqh6- CVSS 3.1
- 7.3 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-471
- Updated
- 2026-11-02
OSVGHSA-r47g-fvhr-h676
6.1
DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM
CWE: CWE-79 (XSS — Improper Neutralization of Input During Web Page Generation) via CWE-693 (Protection Mechanism Failure — silent no-op when _forceRemove is called on a parent-less node)
Summary
When DOMPurify.sanitize(root, { IN_PLACE: true }) is called and root is a <form> whose own attributes carry an event handler (onmouseover, onfocus, onclick, etc.), a single descendant element with a name= attribute matching any of the property names _isClobbered checks (nodeName, setAttribute, namespaceURI, insertBefore, hasChildNodes, childNodes) is sufficient to bypass attribute sanitization on the root. _forceRemove silently no-ops because the root has no parent; the iterator drives on to _sanitizeAttributes, which early-returns on clobbered nodes — and the event handler attribute is never inspected. The sanitized return is the same root, with the handler live.
This affects current main at 89da34e (the just-landed DOM-clobbering hardening fix at 89da34e addressed _sanitizeAttachedShadowRoots walk traversal, not the main _sanitizeElements / _sanitizeAttributes pipeline against the iterator-root node).
Affected
- DOMPurify ≤ 3.4.5, including
mainat89da34e03ec17868e561f87f3747a9371b61a9e7 - Any caller that does
DOMPurify.sanitize(node, { IN_PLACE: true })wherenodeis built from untrusted HTML (e.g., parsed viacreateElement('template').innerHTML = dirtythentemplate.content.firstElementChildhanded in)
Not affected:
- String-input
DOMPurify.sanitize(dirtyString)— the library builds the DOM itself inside_initDocument, the root is the cleanly-created document body, and clobber-named children of the body cannot shadowbodynamed properties (HTMLBodyElement does not carry[LegacyOverrideBuiltIns]) - IN_PLACE where the root is not an HTMLFormElement
- IN_PLACE where the attacker cannot place a clobber-named child inside the root
Vulnerability details
Code paths
[A] — _forceRemove at src/purify.ts:930-939:
const _forceRemove = function (node: Node): void {
arrayPush(DOMPurify.removed, { element: node });
try {
// eslint-disable-next-line unicorn/prefer-dom-node-remove
getParentNode(node).removeChild(node); // [A1] throws when getParentNode returns null
} catch (_) {
remove(node); // [A2] WebIDL Node.remove() — spec-defined no-op
} // when the node has no parent
};
When the iterator-root has no parent (the standard IN_PLACE case where the caller hands in a detached node), getParentNode(node) returns null, null.removeChild(node) throws, the catch falls to remove(node) — which per WebIDL is Element.prototype.remove.call(node), and per spec does nothing if the node has no parent. Nothing about _forceRemove's contract acknowledges this — the function appears to its callers as "the node is gone now," but the node is still in place.
[B] — _sanitizeAttributes at src/purify.ts:1490-1492:
const _sanitizeAttributes = function (currentNode: Element): void {
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
const { attributes } = currentNode;
/* Check if we have attributes; if not we might have a text node */
if (!attributes || _isClobbered(currentNode)) {
return; // [B] silently skips ALL attribute checks
} // for clobbered nodes
...
};
The skip at [B] is deliberate — the intent is to avoid touching nodes the library has already decided to discard. The invariant the comment implies is "if _isClobbered, then _sanitizeElements already removed this node, so we will never reach _sanitizeAttributes on it." That invariant holds for every non-root node…
- Record
GHSA-r47g-fvhr-h676- 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-1321, CWE-693, CWE-79
- Updated
- 2026-11-02
OSVGHSA-vxr8-fq34-vvx9
2.1
DOMPurify: Trusted Types policy survives `clearConfig()` and can poison later `RETURN_TRUSTED_TYPE` output
Impact
A DOMPurify instance that is reused across trust boundaries can stay bound to a previously supplied TRUSTED_TYPES_POLICY even after clearConfig() is called. A later caller that requests RETURN_TRUSTED_TYPE receives a TrustedHTML object created by the old policy, not by a clean default configuration.
If the old policy is unsafe or controlled by a less-trusted integration, this turns a later "default" sanitize call into script execution at a Trusted Types sink. TRUSTED_TYPES_POLICY: null on the later call also does not clear the retained policy.
dompurify-trusted-types-policy-survives-clearconfig-poc.js
Affected version
Tested against DOMPurify 3.4.8, repository commit 825e617753ac1169306a542d3174a77f717a0cf6.
Root cause
_parseConfig() overwrites trustedTypesPolicy when cfg.TRUSTED_TYPES_POLICY is truthy, but the default/null path only initializes the internal policy when trustedTypesPolicy === undefined. Once a custom policy has been set, later default config parsing leaves it in place.
Relevant code:
src/purify.ts:786-812accepts and storescfg.TRUSTED_TYPES_POLICY.src/purify.ts:813-832does not reset an existing policy when config has no policy or hasTRUSTED_TYPES_POLICY: null.src/purify.ts:2123-2125signs the final serialized HTML with the retained policy whenRETURN_TRUSTED_TYPEis true.src/purify.ts:2133-2136clearConfig()only clearsCONFIGandSET_CONFIG; it does not resettrustedTypesPolicyoremptyHTML.
Local PoC
Run from the DOMPurify checkout, or set DOMPURIFY_REPO:
node /home/dompurify-trusted-types-policy-survives-clearconfig-poc.js
Observed output:
{
"result": {
"baseline": "<b>baseline</b>",
"duringPolicy": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"afterClearString": "<img src=\"x\">",
"afterClearTrustedType": "[object TrustedHTML]",
"afterClearTrusted": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"afterNullTrusted": "<img src=x onerror=alert(\"TT_POLICY_SURVIVED_CLEARCONFIG\")>",
"mountedHTML": "<img src=\"x\" onerror=\"alert("TT_POLICY_SURVIVED_CLEARCONFIG")\">"
},
"dialogs": [
"TT_POLICY_SURVIVED_CLEARCONFIG"
]
}
The important part is the split behavior after cleanup:
purify.clearConfig(); purify.sanitize(...);returns a normal sanitized string (<img src="x">), because the later call is not asking for a Trusted Type.purify.clearConfig(); purify.sanitize(..., { RETURN_TRUSTED_TYPE: true });still uses the old policy and returns attacker-controlledTrustedHTML.- Passing
{ TRUSTED_TYPES_POLICY: null, RETURN_TRUSTED_TYPE: true }also still returns attacker-controlledTrustedHTML.
Preconditions
This is a shared-instance state contamination issue. It matters when one DOMPurify instance is reused by multiple integrations, plugins, request handlers, or components with different trust levels, and a cleanup step relies on clearConfig() to restore safe defaults.
This is not a default string-input bypass. An attacker must be able to influence a prior TRUSTED_TYPES_POLICY on the reused instance, or a less-trusted integration must have installed an unsafe policy.
Severity
impact is XSS at a Trusted Types sink in applications that reuse a DOMPurify instance across trust boundaries. Attack complexity is high because exploitation depends on prior policy injection or a less-trusted integration and a later RETURN_TRUSTED_TYPE sink.
Suggested fix
Make clearConfig() reset Trusted Types state as part of restoring defaults, or have _parseConfig() explicitly clear trustedTypesPolicy and emptyHTML when TRUSTED_TYPES_POLICY: null is supplied.
- Record
GHSA-vxr8-fq34-vvx9- CVSS 4.0
- 2.1 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-693
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-49459
6.1
DOMPurify is a DOM-only cross-site scripting sanitizer for HTML, MathML, and SVG. Prior to 3.4.6, DOMPurify.sanitize(root, { IN_PLACE: true }) could preserve event-handler attributes on an attacker-controlled <form> root when a descendant name clobbered properties checked by _isClobbered, because _forceRemove no-opped on the parent-less root and _sanitizeAttributes returned early. This issue is fixed in version 3.4.6.
- Record
UBUNTU-CVE-2026-49459- 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-1774743817 No score
TuxCare security update for handlebars (1 CVE)
TuxCare rebuilt handlebars to address 1 CVE. Fixed in 1.3.0-tuxcare.1.
- Record
CLSA-2026-1774743817- Updated
- 2026-11-02
OSVCLSA-2026-1774743830 No score
TuxCare security update for handlebars (3 CVEs)
TuxCare rebuilt handlebars to address 3 CVEs. Fixed in 1.0.12-tuxcare.2.
- Record
CLSA-2026-1774743830- Updated
- 2026-11-02
OSVCLSA-2026-1784164098 No score
TuxCare security update for dompurify (5 CVEs)
TuxCare rebuilt dompurify to address 5 CVEs. Fixed in 3.1.6-tuxcare.4.
- Record
CLSA-2026-1784164098- Updated
- 2026-11-02
OSVCLSA-2026-1784509816 No score
TuxCare security update for dompurify (5 CVEs)
TuxCare rebuilt dompurify to address 5 CVEs. Fixed in 2.5.8-tuxcare.4.
- Record
CLSA-2026-1784509816- Updated
- 2026-11-02
OSVCLSA-2026-1784596279 No score
TuxCare security update for dompurify (7 CVEs)
TuxCare rebuilt dompurify to address 7 CVEs. Fixed in 3.0.3-tuxcare.2.
- Record
CLSA-2026-1784596279- Updated
- 2026-11-02
OSVCLSA-2026-1784596280 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.7-tuxcare.2.
- Record
CLSA-2026-1784596280- Updated
- 2026-11-02
OSVCLSA-2026-1784596304 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.3-tuxcare.4.
- Record
CLSA-2026-1784596304- Updated
- 2026-11-02
OSVCLSA-2026-1784596306 No score
TuxCare security update for dompurify (3 CVEs)
TuxCare rebuilt dompurify to address 3 CVEs. Fixed in 2.5.9-tuxcare.4.
- Record
CLSA-2026-1784596306- Updated
- 2026-11-02
OSVCLSA-2026-1784596347 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.0-tuxcare.2.
- Record
CLSA-2026-1784596347- Updated
- 2026-11-02
OSVCLSA-2026-1784596351 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.5.8-tuxcare.5.
- Record
CLSA-2026-1784596351- Updated
- 2026-11-02
OSVCLSA-2026-1784682972 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.3.0-tuxcare.3.
- Record
CLSA-2026-1784682972- Updated
- 2026-11-02
OSVCLSA-2026-1784682973 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.7-tuxcare.3.
- Record
CLSA-2026-1784682973- Updated
- 2026-11-02
OSVCLSA-2026-1784682987 No score
TuxCare security update for dompurify (8 CVEs)
TuxCare rebuilt dompurify to address 8 CVEs. Fixed in 3.2.3-tuxcare.2.
- Record
CLSA-2026-1784682987- Updated
- 2026-11-02
OSVCLSA-2026-1784683092 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.3-tuxcare.5.
- Record
CLSA-2026-1784683092- Updated
- 2026-11-02
OSVCLSA-2026-1784769526 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.4.0-tuxcare.3.
- Record
CLSA-2026-1784769526- Updated
- 2026-11-02
OSVCLSA-2026-1784856036 No score
TuxCare security update for dompurify (1 CVE)
TuxCare rebuilt dompurify to address 1 CVE. Fixed in 2.3.0-tuxcare.4.
- Record
CLSA-2026-1784856036- Updated
- 2026-11-02
OSVCLSA-2026-1784856439 No score
TuxCare security update for dompurify (2 CVEs)
TuxCare rebuilt dompurify to address 2 CVEs. Fixed in 2.4.7-tuxcare.4.
- Record
CLSA-2026-1784856439- Updated
- 2026-11-02
OSVCLSA-2026-1786636721 No score
TuxCare security update for dompurify (14 CVEs)
TuxCare rebuilt dompurify to address 14 CVEs. Fixed in 2.4.7-tuxcare.1.
- Record
CLSA-2026-1786636721- Updated
- 2026-11-02
OSVCLSA-2026-1786731948 No score
TuxCare security update for dompurify (34 CVEs)
TuxCare rebuilt dompurify to address 34 CVEs. Fixed in 3.0.3-tuxcare.1.
- Record
CLSA-2026-1786731948- Updated
- 2026-11-02
OSVCLSA-2026-1789139223 No score
TuxCare security update for dompurify (3 CVEs)
TuxCare rebuilt dompurify to address 3 CVEs. Fixed in 3.2.7-tuxcare.2.
- Record
CLSA-2026-1789139223- Updated
- 2026-11-02
OSVCLSA-2026-1789152222 No score
TuxCare security update for dompurify (11 CVEs)
TuxCare rebuilt dompurify to address 11 CVEs. Fixed in 2.3.0-tuxcare.5.
- Record
CLSA-2026-1789152222- Updated
- 2026-11-02
OSVCLSA-2026-1789154068 No score
TuxCare security update for dompurify (12 CVEs)
TuxCare rebuilt dompurify to address 12 CVEs. Fixed in 3.0.3-tuxcare.5.
- Record
CLSA-2026-1789154068- Updated
- 2026-11-02
OSVCLSA-2026-1789154458 No score
TuxCare security update for dompurify (9 CVEs)
TuxCare rebuilt dompurify to address 9 CVEs. Fixed in 2.4.7-tuxcare.5.
- Record
CLSA-2026-1789154458- Updated
- 2026-11-02
OSVCLSA-2026-1789155659 No score
TuxCare security update for dompurify (12 CVEs)
TuxCare rebuilt dompurify to address 12 CVEs. Fixed in 2.4.3-tuxcare.8.
- Record
CLSA-2026-1789155659- Updated
- 2026-11-02
OSVCLSA-2026-1789155876 No score
TuxCare security update for dompurify (8 CVEs)
TuxCare rebuilt dompurify to address 8 CVEs. Fixed in 2.5.8-tuxcare.7.
- Record
CLSA-2026-1789155876- Updated
- 2026-11-02
OSVCLSA-2026-1792150209 No score
TuxCare security update for dompurify (9 CVEs)
TuxCare rebuilt dompurify to address 9 CVEs. Fixed in 3.4.5-tuxcare.2.
- Record
CLSA-2026-1792150209- Updated
- 2026-11-02
OSVCLSA-2026-1792156500 No score
TuxCare security update for dompurify (6 CVEs)
TuxCare rebuilt dompurify to address 6 CVEs. Fixed in 2.0.8-tuxcare.2.
- Record
CLSA-2026-1792156500- Updated
- 2026-11-02
OSVCLSA-2026-1792157349 No score
TuxCare security update for dompurify (5 CVEs)
TuxCare rebuilt dompurify to address 5 CVEs. Fixed in 3.4.6-tuxcare.2.
- Record
CLSA-2026-1792157349- Updated
- 2026-11-02
OSVCLSA-2026-1792157367 No score
TuxCare security update for dompurify (4 CVEs)
TuxCare rebuilt dompurify to address 4 CVEs. Fixed in 3.4.7-tuxcare.2.
- Record
CLSA-2026-1792157367- Updated
- 2026-11-02
OSVCLSA-2026-1792159369 No score
TuxCare security update for dompurify (5 CVEs)
TuxCare rebuilt dompurify to address 5 CVEs. Fixed in 3.3.3-tuxcare.2.
- Record
CLSA-2026-1792159369- Updated
- 2026-11-02
OSVCLSA-2026-1792161492 No score
TuxCare security update for dompurify (10 CVEs)
TuxCare rebuilt dompurify to address 10 CVEs. Fixed in 3.2.6-tuxcare.2.
- Record
CLSA-2026-1792161492- Updated
- 2026-11-02
OSVCLSA-2026-1792424449 No score
TuxCare security update for dompurify (12 CVEs)
TuxCare rebuilt dompurify to address 12 CVEs. Fixed in 2.3.3-tuxcare.3.
- Record
CLSA-2026-1792424449- Updated
- 2026-11-05
OSVCLSA-2026-1792791410 No score
TuxCare security update for dompurify (2 CVEs)
TuxCare rebuilt dompurify to address 2 CVEs. Fixed in 2.4.0-tuxcare.8.
- Record
CLSA-2026-1792791410- Updated
- 2026-11-02
OSVCLSA-2026-1792929243 No score
TuxCare security update for dompurify (7 CVEs)
TuxCare rebuilt dompurify to address 7 CVEs. Fixed in 3.0.6-tuxcare.5.
- Record
CLSA-2026-1792929243- Updated
- 2026-11-02
OSVCLSA-2026-1792933945 No score
TuxCare security update for dompurify (12 CVEs)
TuxCare rebuilt dompurify to address 12 CVEs. Fixed in 3.4.0-tuxcare.1.
- Record
CLSA-2026-1792933945- Updated
- 2026-11-02
OSVCLSA-2026-1793101871 No score
TuxCare security update for dompurify (11 CVEs)
TuxCare rebuilt dompurify to address 11 CVEs. Fixed in 3.0.5-tuxcare.4.
- Record
CLSA-2026-1793101871- Updated
- 2026-11-02
OSVCLSA-2026-1793102041 No score
TuxCare security update for dompurify (11 CVEs)
TuxCare rebuilt dompurify to address 11 CVEs. Fixed in 3.0.4-tuxcare.4.
- Record
CLSA-2026-1793102041- Updated
- 2026-11-02
OSVGHSA-g9r4-xpmj-mj65 No score
Prototype Pollution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to prototype pollution. It is possible to add or modify properties to the Object prototype through a malicious template. This may allow attackers to crash the application or execute Arbitrary Code in specific conditions.
Recommendation
Upgrade to version 3.0.8, 4.5.3 or later.
- Record
GHSA-g9r4-xpmj-mj65- Weakness
- CWE-1321
- Updated
- 2026-11-02
OSVGHSA-q2c6-c6pm-g3gh No score
Arbitrary Code Execution in handlebars
Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It is due to an incomplete fix for a previous issue. This vulnerability can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).
Recommendation
Upgrade to version 3.0.8, 4.5.3 or later.
- Record
GHSA-q2c6-c6pm-g3gh- Updated
- 2026-11-02
OSVGHSA-x4vx-rjvf-j5p4 No score
DOMPurify: `IN_PLACE` mode trusts attacker-controlled `nodeName` on live non-form nodes, allowing script retention and XSS via attacker-supplied DOM objects
Summary
When DOMPurify.sanitize(root, { IN_PLACE: true }) is called on an attacker-supplied live DOM node, DOMPurify still trusts currentNode.nodeName for non-form nodes in the main _sanitizeElements pipeline. A real <script> child node whose observable nodeName is attacker-controlled can therefore be misclassified as an allowed element and retained. When the sanitized tree is inserted into a live document, the script executes.
This affects current 3.4.6. The recent IN_PLACE hardening work covers clobbered form handling and foreign-realm shadow/template traversal, but does not harden the main per-node element decision for hostile non-form live nodes.
Affected
- DOMPurify
3.4.6 - Any caller that does
DOMPurify.sanitize(node, { IN_PLACE: true })on attacker-supplied live DOM nodes - Verified attacker-controlled node sources:
- same-origin
iframe→ live node passed by reference - same-origin
window.open()popup → live node passed by reference - same-origin foreign node adopted into the host document via
document.adoptNode(node)and then sanitized in-place
- same-origin
Not affected:
- String-input
DOMPurify.sanitize(dirtyString)
Vulnerability details
Code paths
[A] — _sanitizeElements uses the instance-visible nodeName for the allow/forbid decision:
const _sanitizeElements = function (currentNode: any): boolean {
...
if (_isClobbered(currentNode)) {
_forceRemove(currentNode);
return true;
}
const tagName = transformCaseFunc(currentNode.nodeName);
...
if (
FORBID_TAGS[tagName] ||
(!(...) && !ALLOWED_TAGS[tagName])
) {
...
_forceRemove(currentNode);
return true;
}
...
};
For non-form nodes, _isClobbered(currentNode) returns false early. The subsequent element decision therefore trusts currentNode.nodeName directly.
[B] — _isClobbered is form-specific:
const _isClobbered = function (element: Element): boolean {
const realTagName = getNodeName ? getNodeName(element) : null;
if (typeof realTagName !== 'string') {
return false;
}
if (transformCaseFunc(realTagName) !== 'form') {
return false;
}
return (...);
};
The hardening is intentionally scoped to form. Non-form nodes are not checked for divergence between the instance-visible property view and the trusted prototype getter view.
Why the bypass works
The attack does not depend on string HTML parsing. It depends on a hostile live DOM object crossing a trust boundary into DOMPurify's IN_PLACE pipeline.
If the attacker controls a same-origin subcontext (iframe or popup), they can prepare a real DOM subtree there and then pass the live node object by reference to a host page that trusts DOMPurify.sanitize(node, { IN_PLACE: true }) as its final sanitization step.
For the verified primitive below:
- the real child node is
<script> - its script text is attacker-controlled
- the observable
nodeNameis attacker-controlled and made to appear as"DIV" _sanitizeElementstherefore classifies the real<script>child as an allowed element- the real
<script>survives in the sanitized tree and executes on insertion
This primitive survives:
- direct reference passing
document.adoptNode(node)followed byIN_PLACE
It does not survive:
importNodecloneNode
because those paths materialize a fresh node and discard the hostile object semantics.
Proof of concept
(1) Minimal — runnable in a single browser context
<!doctype html>
<html><body>
<script src="dist/purify.js"></script>
<script>
const foreign = window.open('about:blank', '_blank', 'noopener=no');
const host = foreign.document.createElement('div');
const script = foreign.document.createElement('script');
script.textContent = 'window.__pwned = 1';
Object.defineProperty(script, 'nodeName', {
value: 'DIV',
configurable: true,
});
host.appendChild(script);
DOMPurify.sanitize(host, {…
- Record
GHSA-x4vx-rjvf-j5p4- Weakness
- CWE-79
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
handlebarsnpm |
before 3.0.8 | 3.0.8 |
handlebarsnpm |
4.0.0 up to 4.5.2 | 4.5.2 |
dompurifynpm |
before 3.4.7 | 3.4.7 |
dompurifynpm |
through 3.4.10 | 3.4.11 |
handlebarsnpm |
4.0.0 up to 4.5.3 | 4.5.3 |
dompurifynpm |
3.0.0 to 3.4.7 | 3.4.8 |
handlebarsnpm |
4.1.0 up to 4.1.2 | 4.1.2 |
handlebarsnpm |
4.0.0 up to 4.0.14 | 4.0.14 |
handlebarsnpm |
before 3.0.7 | 3.0.7 |
dompurifynpm |
through 3.4.5 | 3.4.6 |
Show 55 moreShow less
| Package | Affected | Fixed |
|---|---|---|
dompurifynpm |
before 3.4.9 | 3.4.9 |
dompurifynpm |
through 3.4.6 | None known |
handlebarsTuxCare:npm |
1.3.0-tuxcare.0 up to 1.3.0-tuxcare.1, plus listed versions 1.3.0 |
1.3.0-tuxcare.1 |
handlebarsTuxCare:npm |
1.0.12-tuxcare.0 up to 1.0.12-tuxcare.2, plus listed versions 1.0.121.0.12-tuxcare.1 |
1.0.12-tuxcare.2 |
dompurifyTuxCare:npm |
3.1.6-tuxcare.0 up to 3.1.6-tuxcare.4, plus listed versions 3.1.63.1.6-tuxcare.13.1.6-tuxcare.23.1.6-tuxcare.3 |
3.1.6-tuxcare.4 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
2.5.8-tuxcare.0 up to 2.5.8-tuxcare.4, plus listed versions 2.5.82.5.8-tuxcare.12.5.8-tuxcare.22.5.8-tuxcare.3 |
2.5.8-tuxcare.4 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
3.0.3-tuxcare.0 up to 3.0.3-tuxcare.2, plus listed versions 3.0.33.0.3-tuxcare.1 |
3.0.3-tuxcare.2 |
dompurifyTuxCare:npm |
2.4.7-tuxcare.0 up to 2.4.7-tuxcare.2, plus listed versions 2.4.72.4.7-tuxcare.1 |
2.4.7-tuxcare.2 |
dompurifyTuxCare:npm |
2.4.3-tuxcare.0 up to 2.4.3-tuxcare.4, plus listed versions 2.4.32.4.3-tuxcare.12.4.3-tuxcare.22.4.3-tuxcare.3 |
2.4.3-tuxcare.4 |
dompurifyTuxCare:npm |
2.5.9-tuxcare.0 up to 2.5.9-tuxcare.4, plus listed versions 2.5.92.5.9-tuxcare.12.5.9-tuxcare.22.5.9-tuxcare.3 |
2.5.9-tuxcare.4 |
dompurifyTuxCare:npm |
2.4.0-tuxcare.0 up to 2.4.0-tuxcare.2, plus listed versions 2.4.02.4.0-tuxcare.1 |
2.4.0-tuxcare.2 |
dompurifyTuxCare:npm |
2.5.8-tuxcare.0 up to 2.5.8-tuxcare.5, plus listed versions 2.5.82.5.8-tuxcare.12.5.8-tuxcare.22.5.8-tuxcare.32.5.8-tuxcare.4 |
2.5.8-tuxcare.5 |
dompurifyTuxCare:npm |
2.3.0-tuxcare.0 up to 2.3.0-tuxcare.3, plus listed versions 2.3.02.3.0-tuxcare.12.3.0-tuxcare.2 |
2.3.0-tuxcare.3 |
dompurifyTuxCare:npm |
2.4.7-tuxcare.0 up to 2.4.7-tuxcare.3, plus listed versions 2.4.72.4.7-tuxcare.12.4.7-tuxcare.2 |
2.4.7-tuxcare.3 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
3.2.3-tuxcare.0 up to 3.2.3-tuxcare.2, plus listed versions 3.2.33.2.3-tuxcare.1 |
3.2.3-tuxcare.2 |
dompurifyTuxCare:npm |
2.4.3-tuxcare.0 up to 2.4.3-tuxcare.5, plus listed versions 2.4.32.4.3-tuxcare.12.4.3-tuxcare.22.4.3-tuxcare.32.4.3-tuxcare.4 |
2.4.3-tuxcare.5 |
dompurifyTuxCare:npm |
2.4.0-tuxcare.0 up to 2.4.0-tuxcare.3, plus listed versions 2.4.02.4.0-tuxcare.12.4.0-tuxcare.2 |
2.4.0-tuxcare.3 |
dompurifyTuxCare:npm |
2.3.0-tuxcare.0 up to 2.3.0-tuxcare.4, plus listed versions 2.3.02.3.0-tuxcare.12.3.0-tuxcare.22.3.0-tuxcare.3 |
2.3.0-tuxcare.4 |
dompurifyTuxCare:npm |
2.4.7-tuxcare.0 up to 2.4.7-tuxcare.4, plus listed versions 2.4.72.4.7-tuxcare.12.4.7-tuxcare.22.4.7-tuxcare.3 |
2.4.7-tuxcare.4 |
dompurifyTuxCare:npm From an advisory covering 3 CVEs |
3.2.7-tuxcare.0 up to 3.2.7-tuxcare.2, plus listed versions 3.2.73.2.7-tuxcare.1 |
3.2.7-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 10 CVEs |
2.3.0-tuxcare.0 up to 2.3.0-tuxcare.5, plus listed versions 2.3.02.3.0-tuxcare.12.3.0-tuxcare.22.3.0-tuxcare.32.3.0-tuxcare.4 |
2.3.0-tuxcare.5 |
dompurifyTuxCare:npm From an advisory covering 11 CVEs |
3.0.3-tuxcare.0 up to 3.0.3-tuxcare.5, plus listed versions 3.0.33.0.3-tuxcare.13.0.3-tuxcare.23.0.3-tuxcare.33.0.3-tuxcare.4 |
3.0.3-tuxcare.5 |
dompurifyTuxCare:npm From an advisory covering 8 CVEs |
2.4.7-tuxcare.0 up to 2.4.7-tuxcare.5, plus listed versions 2.4.72.4.7-tuxcare.12.4.7-tuxcare.22.4.7-tuxcare.32.4.7-tuxcare.4 |
2.4.7-tuxcare.5 |
dompurifyTuxCare:npm From an advisory covering 11 CVEs |
2.4.3-tuxcare.0 up to 2.4.3-tuxcare.8, plus listed versions 2.4.32.4.3-tuxcare.12.4.3-tuxcare.22.4.3-tuxcare.32.4.3-tuxcare.42.4.3-tuxcare.52.4.3-tuxcare.62.4.3-tuxcare.7 |
2.4.3-tuxcare.8 |
dompurifyTuxCare:npm From an advisory covering 7 CVEs |
2.5.8-tuxcare.0 up to 2.5.8-tuxcare.7, plus listed versions 2.5.82.5.8-tuxcare.12.5.8-tuxcare.22.5.8-tuxcare.32.5.8-tuxcare.42.5.8-tuxcare.52.5.8-tuxcare.6 |
2.5.8-tuxcare.7 |
dompurifyTuxCare:npm From an advisory covering 6 CVEs |
3.4.5-tuxcare.0 up to 3.4.5-tuxcare.2, plus listed versions 3.4.53.4.5-tuxcare.1 |
3.4.5-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 6 CVEs |
2.0.8-tuxcare.0 up to 2.0.8-tuxcare.2, plus listed versions 2.0.82.0.8-tuxcare.1 |
2.0.8-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 4 CVEs |
3.4.6-tuxcare.0 up to 3.4.6-tuxcare.2, plus listed versions 3.4.63.4.6-tuxcare.1 |
3.4.6-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 3 CVEs |
3.4.7-tuxcare.0 up to 3.4.7-tuxcare.2, plus listed versions 3.4.73.4.7-tuxcare.1 |
3.4.7-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 5 CVEs |
3.3.3-tuxcare.0 up to 3.3.3-tuxcare.2, plus listed versions 3.3.33.3.3-tuxcare.1 |
3.3.3-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 10 CVEs |
3.2.6-tuxcare.0 up to 3.2.6-tuxcare.2, plus listed versions 3.2.63.2.6-tuxcare.1 |
3.2.6-tuxcare.2 |
dompurifyTuxCare:npm From an advisory covering 12 CVEs |
2.3.3-tuxcare.0 up to 2.3.3-tuxcare.3, plus listed versions 2.3.32.3.3-tuxcare.12.3.3-tuxcare.2 |
2.3.3-tuxcare.3 |
dompurifyTuxCare:npm From an advisory covering 2 CVEs |
2.4.0-tuxcare.0 up to 2.4.0-tuxcare.8, plus listed versions 2.4.02.4.0-tuxcare.12.4.0-tuxcare.22.4.0-tuxcare.32.4.0-tuxcare.42.4.0-tuxcare.52.4.0-tuxcare.62.4.0-tuxcare.7 |
2.4.0-tuxcare.8 |
dompurifyTuxCare:npm From an advisory covering 5 CVEs |
3.0.6-tuxcare.0 up to 3.0.6-tuxcare.5, plus listed versions 3.0.63.0.6-tuxcare.13.0.6-tuxcare.23.0.6-tuxcare.33.0.6-tuxcare.4 |
3.0.6-tuxcare.5 |
dompurifyTuxCare:npm From an advisory covering 9 CVEs |
3.4.0-tuxcare.0 up to 3.4.0-tuxcare.1, plus listed versions 3.4.0 |
3.4.0-tuxcare.1 |
dompurifyTuxCare:npm From an advisory covering 10 CVEs |
3.0.5-tuxcare.0 up to 3.0.5-tuxcare.4, plus listed versions 3.0.53.0.5-tuxcare.13.0.5-tuxcare.23.0.5-tuxcare.3 |
3.0.5-tuxcare.4 |
dompurifyTuxCare:npm From an advisory covering 10 CVEs |
3.0.4-tuxcare.0 up to 3.0.4-tuxcare.4, plus listed versions 3.0.43.0.4-tuxcare.13.0.4-tuxcare.23.0.4-tuxcare.3 |
3.0.4-tuxcare.4 |
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.12+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-23.4.1+dfsg-13.4.2+dfsg-13.4.5+dfsg-1 |
3.4.12+dfsg-1 |
dompurifynpm |
before 3.4.11 | 3.4.11 |
dompurifynpm |
3.0.0 up to 3.4.8 | 3.4.8 |
dompurifynpm |
before 3.4.6 | 3.4.6 |
dompurifyRoot:npm |
before 2.5.8-aikido.3 | 2.5.8-aikido.32.5.9-aikido.23.2.4-aikido.22.5.9-aikido.33.2.4-aikido.32.5.8-aikido.23.4.0-aikido.13.4.2-aikido.13.4.5-aikido.13.4.2-aikido.22.5.9-aikido.43.2.4-aikido.42.5.9-aikido.53.2.4-aikido.53.4.6-aikido.23.4.6-aikido.13.4.9-aikido.13.4.7-aikido.13.4.8-aikido.13.4.10-aikido.1 |
@rootio/dompurifyRoot:npm |
before 2.5.8-root.io.3 | 2.5.8-root.io.32.5.9-root.io.23.2.4-root.io.22.5.9-root.io.33.2.4-root.io.32.5.8-root.io.23.4.0-root.io.13.4.2-root.io.13.4.5-root.io.13.4.2-root.io.22.5.9-root.io.43.2.4-root.io.42.5.9-root.io.53.2.4-root.io.53.4.6-root.io.23.4.6-root.io.13.4.9-root.io.13.4.7-root.io.13.4.8-root.io.13.4.10-root.io.1 |
handlebarsRoot:npm |
before 3.0.7-aikido.3 | 3.0.7-aikido.33.0.3-aikido.23.0.8-aikido.13.0.8-aikido.2 |
@rootio/handlebarsRoot:npm |
before 3.0.7-root.io.3 | 3.0.7-root.io.33.0.3-root.io.23.0.8-root.io.13.0.8-root.io.2 |
handlebarsRoot:npm |
before 3.0.3-aikido.2 | 3.0.3-aikido.23.0.7-aikido.13.0.7-aikido.23.0.7-aikido.3 |
@rootio/handlebarsRoot:npm |
before 3.0.3-root.io.2 | 3.0.3-root.io.23.0.7-root.io.13.0.7-root.io.23.0.7-root.io.3 |
dompurify.jsUbuntu:16.04:LTS |
All versions, plus listed versions 0.7.3~dfsg1-10.7.3~dfsg1-2 |
None known |
dompurify.jsUbuntu:18.04:LTS |
All versions, plus listed versions 0.9.0~dfsg1-10.9.0~dfsg1-2 |
None known |
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 | OSV | |
| affected | ||
| 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-49459 is a medium vulnerability (CVSS 6.1) in Cure53 Dompurify, published June 5, 2019. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.36%, the 30th percentile. Fixed in handlebars 3.0.8.
References
github.com/cure53/DOMPurify/security/advisories/GHSA-r47g-fvhr-h676
github.com/cure53/DOMPurify/commit/bb7739e5bccec7e1ab3dae3f3e42d02db3acaaae
github.com/cure53/DOMPurify/releases/tag/3.4.6
npmjs.com/advisories/1316
github.com/advisories/GHSA-2cf5-4w76-r9qv
github.com/cure53/DOMPurify/security/advisories/GHSA-76mc-f452-cxcm
Show 31 moreShow less
github.com/advisories/GHSA-76mc-f452-cxcm
github.com/cure53/DOMPurify/security/advisories/GHSA-cmwh-pvxp-8882
github.com/cure53/DOMPurify/pull/1482
github.com/cure53/DOMPurify/commit/011bc3b2fcc4be17aa76f9030e8668207717acb9
github.com/cure53/DOMPurify/releases/tag/3.4.11
github.com/advisories/GHSA-cmwh-pvxp-8882
npmjs.com/advisories/1325
github.com/advisories/GHSA-g9r4-xpmj-mj65
github.com/cure53/DOMPurify/security/advisories/GHSA-gvmj-g25r-r7wr
github.com/advisories/GHSA-gvmj-g25r-r7wr
npmjs.com/advisories/1324
github.com/advisories/GHSA-q2c6-c6pm-g3gh
github.com/handlebars-lang/handlebars.js/issues/1495
github.com/handlebars-lang/handlebars.js/commit/7372d4e9dffc9d70c09671aa28b9392a1577fd86
snyk.io/vuln/SNYK-JS-HANDLEBARS-173692
npmjs.com/advisories/755
github.com/handlebars-lang/handlebars.js/commit/0d6d8c335ad81bad1b672fc56b6a44f6aa472dac
github.com/handlebars-lang/handlebars.js/commit/85c8783b34fc6d36145d8b53885ad0b9e3c3f9c4
github.com/handlebars-lang/handlebars.js/commit/cd38583216dce3252831916323202749431c773e
github.com/advisories/GHSA-q42p-pg8m-cqh6
github.com/advisories/GHSA-r47g-fvhr-h676
github.com/cure53/DOMPurify/security/advisories/GHSA-vxr8-fq34-vvx9
github.com/advisories/GHSA-vxr8-fq34-vvx9
github.com/cure53/DOMPurify/security/advisories/GHSA-x4vx-rjvf-j5p4
github.com/advisories/GHSA-x4vx-rjvf-j5p4
github.com/CVEProject/cvelistV5/tree/main/cves/2026/49xxx/CVE-2026-49459.json
nvd.nist.gov/vuln/detail/CVE-2026-49459
security-tracker.debian.org/tracker/CVE-2026-49459
github.com/cure53/DOMPurify
ubuntu.com/security/CVE-2026-49459
cve.org/CVERecord?id=CVE-2026-49459
Left out here: the rest of 10 source descriptions. The JSON record has them. Left out on purpose, here and in the API, to keep responses small: 8 source records.