Open actionable advisories and recorded vendor fixes. This public package view has no inventory attached and cannot establish whether your deployment is affected. A vendor fix never resolves your finding; verify your installed version and the source guidance.
Open advisories
32 actionable advisories, newest first. Withdrawn advisories and news are excluded. Coverage is partial.
When DOMPurify is configured with `SAFE_FOR_TEMPLATES: true` together with a DOM-returning mode (`RETURN_DOM: true`, `RETURN_DOM_FRAGMENT: true`, or `IN_PLACE: true`), template expressions such as `{{...}}`, `${...}`, and ` ` survive sanitization inside the text content of an attached shadow root. The post-walk safety net `_scrubTemplateExpressions` (`src/purify.ts:1717`) was extended to recurse into ` .content`, but it never recurses into attached shadow roots. An attacker who can supply a DOM node carrying a shadow root (the `IN_PLACE` and shadow-aware `RETURN_DOM` surfaces) can split an expression across text nodes separated by disallowed elements; after those elements are removed the fragments become adjacent and serialize into a complete expression that a downstream template engine will evaluate. Root Cause `SAFE_FOR_TEMPLATES` neutralizes template expressions through two mechanisms: a per-text-node strip during the main walk (`_stripTemplateExpressions`, applied…
Recommended action: Upgrade to 3.4.17 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
The 3.4.16 fix for GHSA-p98j-92pf-mc4p only covers a hook that removes the node it was called for. If an `IN_PLACE` hook removes a different node, like the current node's parent or a sibling that has not been visited yet, that subtree is never walked or neutralized. Event handlers in it stay on the elements, and an ` ` that was already loading fires after `sanitize()` returns. The README says: "Nodes that a hook detaches from the tree ... are treated as removed. In IN_PLACE mode their subtree is neutralized inline, so an ` ` that was already loading when you built the live tree cannot fire after `sanitize()` returns." That does not hold when the detached node is not the hook's own node. Tested versions - 3.4.16 (latest) and main at 18f8f0e - also 3.4.15, 3.4.12 and 2.5.8 jsdom for the matrix below, and Chromium (Playwright) for the event actually firing. Details `src/purify.ts` line 2134 (`dist/purify.cjs.js` line 2009 in 3.4.16), `_handleHookDetachedNode()`:…
Recommended action: Upgrade to 3.4.17 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify drops `attributeName` attributes whose value contains `href`, so an allowed ` ` or ` ` can't rewrite a link's href after sanitizing. The check compares the attribute name after `transformCaseFunc`, and that function doesn't lowercase in XHTML mode. In XHTML the attribute is spelled `attributeName`, so the comparison with `"attributename"` never matches and the guard does nothing. An app that uses `PARSER_MEDIA_TYPE: 'application/xhtml+xml'` and allows `animate` and `attributeName` will keep ` `. Clicking the parent link then runs the script. This needs a non-default config, so going by SECURITY.md, this is a hardening bug rather than a bypass of the default config. Details In `_sanitizeAttributes` (dist/purify.js line 2216 in 3.4.16): ```js const lcName = transformCaseFunc(name); ... if (lcName === "attributename" && stringMatch(value, "href")) { _removeAttribute(name, currentNode, attr); continue; } ``` `transformCaseFunc` is `stringToLowerCase` for…
Recommended action: Upgrade to 3.4.17 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Environment - dompurify 3.4.15 (current npm release); reproduced independently on jsdom 30.0.1 and 29.1.1 (Node.js 20.x / 26.x) - Config: DOMPurify.sanitize(node, { INPLACE: true }) on a Node input; SAFEFORXML at its default (true) Summary The 3.4.9 fix for the INPLACE detached-root class added two protections on the INPLACE return path: a fail-closed TypeError in forceRemove when a node selected for removal cannot be detached, and a neutralizeSubtree pass (dist/purify.js line 1336) that strips non-allowlisted attributes from removed subtrees. Both miss the rawtext text-content form. When the force-removed root is a rawtext element (<style), the payload lives in the node's text: the node detaches fine (the TypeError guard is not reached), neutralizeSubtree strips nothing (there are no attributes), and the INPLACE exit returns the detached, never-sanitized <style whose text still carries live markup. Serializing that node and re-parsing it in plain HTML context materializes the…
Recommended action: Upgrade to 3.4.16 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
In INPLACE 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 INPLACE together with a node-removing afterSanitize hook. Root Cause INPLACE 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…
Recommended action: Upgrade to 3.4.16 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
During INPLACE sanitization, a hook that removes an element can leave that element's detached descendants executable. A descendant image can retain its attacker-provided onload handler and fire after sanitize() returns, even though the returned root is clean and the image remains disconnected from the document. Details In DOMPurify 3.4.12, sanitizeElements() in src/purify.ts:1862-1904 runs the beforeSanitizeElements or uponSanitizeElement hook and returns immediately when the hook detached the current node. The return does not call neutralizeSubtree(currentNode). The detached subtree is not added to DOMPurify.removed, so the post-walk INPLACE neutralization cannot reach it. If the browser queued a resource event while the application constructed the detached dirty root, a descendant can therefore retain its handler and execute after sanitization. The hook only rejects the containing element and does not add or approve the event handler. DOMPurify's ordinary removal path…
Recommended action: Upgrade to 3.4.13 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
There is a possible hook-policy inconsistency in DOMPurify 3.4.11 involving CUSTOMELEMENTHANDLING. When a custom element is allowed via CUSTOMELEMENTHANDLING.tagNameCheck, it appears that the element does not go through afterSanitizeElements in the same way as a normal element. As a result, an application that relies on afterSanitizeElements as a security policy layer to strip sensitive attributes from all elements may see those attributes removed from normal elements but preserved on allowed custom elements. This does not appear to be a direct DOMPurify XSS or a case where DOMPurify directly allows executable payloads. The preserved value is still inert at sanitize time. The issue becomes relevant when the allowed custom element later re-injects that attribute value into an HTML sink such as innerHTML, creating a second-order XSS gadget. Details The issue appears to originate from the control flow in src/purify.ts: line 1672~1691 CUSTOMELEMENTHANDLING is parsed from user…
Recommended action: Upgrade to 3.4.12 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
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 ALLOWEDATTR inside parseConfig. That guard is silently bypassed whenever the application uses the persistent-config API DOMPurify.setConfig(). setConfig() sets the module flag SETCONFIG = true, which causes sanitize() to skip parseConfig entirely — and the clone-guard lives inside parseConfig. The hook is then handed the live, shared ALLOWEDATTR 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,…
Recommended action: Upgrade to 3.4.11 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Impact A DOMPurify instance that is reused across trust boundaries can stay bound to a previously supplied TRUSTEDTYPESPOLICY even after clearConfig() is called. A later caller that requests RETURNTRUSTEDTYPE 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. TRUSTEDTYPESPOLICY: 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.TRUSTEDTYPESPOLICY 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.…
Recommended action: Upgrade to 3.4.9 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
When DOMPurify is configured with both SAFEFORTEMPLATES: true and RETURNDOM: true (or INPLACE: 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 SAFEFORTEMPLATES, which is to prevent template engine evaluation of user-supplied content. Note: The string output path is not affected. Only the DOM return paths (RETURNDOM: true, RETURNDOMFRAGMENT: true, INPLACE: true) are vulnerable. --- Description Background SAFEFORTEMPLATES 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: 1. Per-node scrubbing (sanitizeElements, src/purify.ts:1403), scrubs individual text nodes during the main sanitization walk. 2. Final normalization pass (scrubTemplateExpressions, src/purify.ts:1115), calls…
Recommended action: Upgrade to 3.4.8 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
If the HTML you give it contains a <template element, and inside that template there's an element with a shadow DOM attached to it, DOMPurify quietly skips over the shadow contents. Whatever the attacker put in there - an image with an onerror handler, a link with a javascript: URL, even a full script - survives untouched. The moment the application uses that template the way templates are meant to be used (cloning it and inserting the result into the page), the malicious payload comes along and runs as if it had never been sanitized. From there an attacker gets everything XSS normally gets them: session cookies, stored tokens, the ability to act as the user, and the ability to leave persistent payloads behind for the next person who visits. advisory.pdf poc.html
Recommended action: Upgrade to 3.4.7 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Hook mutation of data.allowedTags / data.allowedAttributes permanently pollutes DEFAULTALLOWEDTAGS / DEFAULTALLOWEDATTR 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 ALLOWEDTAGS / ALLOWEDATTR sets. For sanitize calls that don't supply an explicit cfg.ALLOWEDTAGS / cfg.ALLOWEDATTR array, those live sets are themselves direct references to the module-level DEFAULTALLOWEDTAGS / DEFAULTALLOWEDATTR 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…
Recommended action: Upgrade to 3.4.7 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Cross-realm INPLACE sanitization leaves executable markup intact via realm-bound instanceof checks CWE: CWE-79 (XSS — Improper Neutralization of Input During Web Page Generation) via CWE-693 (Protection Mechanism Failure — realm-bound instanceof checks fail-open on foreign-realm DOM nodes) and CWE-501 (Trust Boundary Violation — foreign-realm nodes accepted for sanitization but later checks are bound to the parent realm) Summary DOMPurify.sanitize(node, { INPLACE: true }) accepts a DOM node from any same-origin realm (e.g. a node owned by an application-created iframe document), but several follow-on security checks compare the node against constructors from the parent realm. Because constructors are per-realm, instanceof HTMLFormElement, instanceof NamedNodeMap, instanceof DocumentFragment, and instanceof Element all return false for nodes belonging to the iframe's realm. The library therefore proceeds as if the foreign-realm form is not clobberable, the foreign-realm <template's…
Recommended action: Upgrade to 3.4.6 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
INPLACE 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, { INPLACE: 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…
Recommended action: Upgrade to 3.4.6 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify 3.4.4 allows selectedcontent by default, allowing a chain in which browsers "re-clone" an XSS payload after sanitization, effectively bypassing DOMPurify. Details The chain is as follows: 1. The browser parses the input and creates a <selectedcontent clone from the selected <option 2. DOMPurify walks and sanitizes that generated clone. 3. DOMPurify reaches the original <option and removes selected=javascript:1 4. The browser refreshes the <selectedcontent clone from the original option's content. 5. The refreshed clone is in a subtree DOMPurify already walked, which DOMPurify doesn't go back to sanitize 6. The returned string contains unsanitized markup inside <selectedcontent. PoC Observed "sanitized" output in Chromium 148/WebKit 625: After reinsertion, the browser updates the live DOM and strips the handler from the displayed clone, but the onerror has already fired: Reproduced in Chromium and WebKit, but not Safari (not yet latest WebKit) or Firefox. Will likely…
Recommended action: Upgrade to 3.4.5 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
There is an inconsistency between FORBIDTAGS and FORBIDATTR handling when function-based ADDTAGS is used. Commit c361baa added an early exit for FORBIDATTR at line 1214: / FORBIDATTR must always win, even if ADDATTR predicate would allow it / if (FORBIDATTR[lcName]) { return false; } The same fix was not applied to FORBIDTAGS. At line 1118-1123, when EXTRAELEMENTHANDLING.tagCheck returns true, the short-circuit evaluation skips the FORBIDTAGS check entirely: if ( !( EXTRAELEMENTHANDLING.tagCheck instanceof Function && EXTRAELEMENTHANDLING.tagCheck(tagName) // true - short-circuits ) && (!ALLOWEDTAGS[tagName] || FORBIDTAGS[tagName]) // never evaluated ) { This allows forbidden elements to survive sanitization with their attributes intact. PoC (tested against current HEAD in Node.js + jsdom): const DOMPurify = createDOMPurify(window); DOMPurify.sanitize( '<iframe src="https://evil.com"</iframe', { ADDTAGS: function(tag) { return true; }, FORBIDTAGS: ['iframe'] } ); // Returns: '<iframe…
Recommended action: Upgrade to 3.4.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
| Field | Value | |:------|:------| | Severity | Medium | | Affected | DOMPurify main at 883ac15, introduced in v1.0.10 (7fc196db) | SAFEFORTEMPLATES strips {{...}} expressions from untrusted HTML. This works in string mode but not with RETURNDOM or RETURNDOMFRAGMENT, allowing XSS via template-evaluating frameworks like Vue 2. Technical Details DOMPurify strips template expressions in two passes: 1. Per-node — each text node is checked during the tree walk (purify.ts:1179-1191): 2. Final string scrub — after serialization, the full HTML string is scrubbed again (purify.ts:1679-1683). This is the safety net that catches expressions that only form after the DOM settles. The RETURNDOM path returns before pass #2 ever runs (purify.ts:1637-1661): The payload {<foo</foo{constructor.constructor('alert(1)')()}<foo</foo} exploits this: 1. Parser creates: TEXT("{") → <foo → TEXT("{payload}") → <foo → TEXT("}") — no single node contains {{, so pass #1 misses it 2. <foo is not allowed,…
Recommended action: Upgrade to 3.4.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify versions 3.0.1 through 3.3.3 (latest) are vulnerable to a prototype pollution-based XSS bypass. When an application uses DOMPurify.sanitize() with the default configuration (no CUSTOMELEMENTHANDLING option), a prior prototype pollution gadget can inject permissive tagNameCheck and attributeNameCheck regex values into Object.prototype, causing DOMPurify to allow arbitrary custom elements with arbitrary attributes — including event handlers — through sanitization. Affected Versions - 3.0.1 through 3.3.3 (current latest) — all affected - 3.0.0 and all 2.x versions — NOT affected (used Object.create(null) for initialization, no || {} reassignment) - The vulnerable || {} reassignment was introduced in the 3.0.0→3.0.1 refactor - This is distinct from GHSA-cj63-jhhr-wcxv (USEPROFILES Array.prototype pollution, fixed in 3.3.2) - This is distinct from CVE-2024-45801 / GHSA-mmhx-hmjr-r674 (depth prototype pollution, fixed in 3.1.3) Root Cause In purify.js at line 590, during…
Recommended action: Upgrade to 3.4.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
In src/purify.ts:1117-1123, ADDTAGS as a function (via EXTRAELEMENTHANDLING.tagCheck) bypasses FORBIDTAGS due to short-circuit evaluation. The condition: When tagCheck(tagName) returns true, the entire condition is false and the element is kept — FORBIDTAGS[tagName] is never evaluated. Inconsistency This contradicts the attribute-side pattern at line 1214 where FORBIDATTR explicitly wins first: For tags, FORBID should also take precedence over ADD. Impact Applications using both ADDTAGS as a function and FORBIDTAGS simultaneously get unexpected behavior — forbidden tags are allowed through. Config-dependent but a genuine logic inconsistency. Suggested Fix Check FORBIDTAGS before tagCheck: Affected Version v3.3.3 (commit 883ac15)
Recommended action: Upgrade to 3.4.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify allows ADDATTR to be provided as a predicate function via EXTRAELEMENTHANDLING.attributeCheck. When the predicate returns true, isValidAttribute short-circuits the attribute check before URI-safe validation runs. An attacker who supplies a predicate that accepts specific attribute/tag combinations can then sanitize input such as <a href="javascript:alert(document.domain)" and have the javascript: URL survive, because URI validation is skipped for that attribute while other checks still pass. The provided PoC accepts href for anchors and then triggers a click inside an iframe, showing that the sanitized payload executes despite the protocol bypass. Impact Predicate-based allowlisting bypasses DOMPurify's URI validation, allowing unsafe protocols such as javascript: to reach the DOM and execute whenever the link is activated, resulting in DOM-based XSS. Credits Identified by Cantina’s Apex (https://www.cantina.security).
Recommended action: Upgrade to 3.3.2 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
When USEPROFILES is enabled, DOMPurify rebuilds ALLOWEDATTR as a plain array before populating it with the requested allowlists. Because the sanitizer still looks up attributes via ALLOWEDATTR[lcName], any Array.prototype property that is polluted also counts as an allowlisted attribute. An attacker who can set Array.prototype.onclick = true (or a runtime already subject to prototype pollution) can thus force DOMPurify to keep event handlers such as onclick even when they are normally forbidden. The provided PoC sanitizes <img onclick=... with USEPROFILES and adds the sanitized output to the DOM; the polluted prototype allows the event handler to survive and execute, turning what should be a blocklist into a silent XSS vector. Impact Prototype pollution makes DOMPurify accept dangerous event handler attributes, which bypasses the sanitizer and results in DOM-based XSS once the sanitized markup is rendered. Credits Identified by Cantina’s Apex (https://www.cantina.security).
Recommended action: Upgrade to 3.3.2 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
A mutation-XSS (mXSS) condition was confirmed when sanitized HTML is reinserted into a new parsing context using innerHTML and special wrappers. The vulnerable wrappers confirmed in browser behavior are script, xmp, iframe, noembed, noframes, and noscript. The payload remains seemingly benign after DOMPurify.sanitize(), but mutates during the second parse into executable markup with an event handler, enabling JavaScript execution in the client (alert(1) in the PoC). Vulnerability The root cause is context switching after sanitization: sanitized output is treated as trusted and concatenated into a wrapper string (for example, <xmp ... </xmp or other special wrappers) before being reparsed by the browser. In this flow, attacker-controlled text inside an attribute (for example </xmp or equivalent closing sequences for each wrapper) closes the special parsing context early and reintroduces attacker markup (<img ... onerror=...) outside the original attribute context.…
Recommended action: Upgrade to 3.3.2 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify 3.1.3 through 3.2.6 and 2.5.3 through 2.5.8 contain a cross-site scripting vulnerability that allows attackers to bypass attribute sanitization by exploiting missing textarea rawtext element validation in the SAFEFORXML regex. Attackers can include closing rawtext tags like </textarea in attribute values to break out of rawtext contexts and execute JavaScript when sanitized output is placed inside rawtext elements. The 3.x branch was fixed in 3.2.7; the 2.x branch was never patched.
Recommended action: Upgrade to 3.2.7; some affected versions have no fix yet, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify 3.1.3 through 3.3.1 and 2.5.3 through 2.5.8, fixed in 2.5.9 and 3.3.2, contain a cross-site scripting vulnerability that allows attackers to bypass attribute sanitization by exploiting five missing rawtext elements (noscript, xmp, noembed, noframes, iframe) in the SAFEFORXML regex. Attackers can include payloads like </noscript<img src=x onerror=alert(1) in attribute values to execute JavaScript when sanitized output is placed inside these unprotected rawtext contexts.
Recommended action: Upgrade to 3.3.2 or 2.5.9 by release line, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
DOMPurify before 3.2.4 has an incorrect template literal regular expression when SAFEFORTEMPLATES is set to true, sometimes leading to mutation cross-site scripting (mXSS).
Recommended action: Upgrade to 3.2.4 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.