Public mode

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

Some sources, documents or advisories need attention: 275/275 feeds/APIs · 469/469 docs · 8/9 advisories · checked 15:09 UTC Customize Public modeDiscuss ChangeIntel on Discord

DOMPurify: Prototype Pollution to XSS Bypass via CUSTOM_ELEMENT_HANDLING Fallback

Exact source revision recorded 24 Oct 2026 13:17:17 UTC. It does not establish the source's current position. Latest evidence.

osv · GHSA-v9jr-rg53-9pgp

Published

## Summary 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 `CUSTOM_ELEMENT_HANDLING` 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 (USE_PROFILES 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 config parsing: ```javascript CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; ``` When no `CUSTOM_ELEMENT_HANDLING` is specified in the config (the default usage pattern), `cfg.CUSTOM_ELEMENT_HANDLING` is `undefined`, and the fallback `{}` is used. This plain object inherits from `Object.prototype`. Lines 591-598 then check `cfg.CUSTOM_ELEMENT_HANDLING` (the original config property) — which is `undefined` — so the conditional blocks that would set `tagNameCheck` and `attributeNameCheck` from the config are never entered. As a result, `CUSTOM_ELEMENT_HANDLING.tagNameCheck` and `CUSTOM_ELEMENT_HANDLING.attributeNameCheck` resolve via the prototype chain. If an attacker has polluted `Object.prototype.tagNameCheck` and `Object.prototype.attributeNameCheck` with permissive values (e.g., `/.*/`), these polluted values flow into DOMPurify's custom element validation at lines 973-977 and attribute validation, causing all custom elements and all attributes to be allowed. ## Impact - **Attack type:** XSS bypass via prototype pollution chain - **Prerequisites:** Attacker must have a prototype pollution primitive in the same execution context (e.g., vulnerable version of lodash, jQuery.extend, query-string parser, deep merge utility, or any other PP gadget) - **Config required:** Default. No special DOMPurify configuration needed. The standard `DOMPurify.sanitize(userInput)` call is affected. - **Payload:** Any HTML custom element (name containing a hyphen) with event handler attributes survives sanitization ## Proof of Concept ```javascript // Step 1: Attacker exploits a prototype pollution gadget elsewhere in the application Object.prototype.tagNameCheck = /.*/; Object.prototype.attributeNameCheck = /.*/; // Step 2: Application sanitizes user input with DEFAULT config const clean = DOMPurify.sanitize('<x-x onfocus=alert(document.cookie) tabindex=0 autofocus>'); // Step 3: "Sanitized" output still contains the event handler console.log(clean); // Output: <x-x onfocus="alert(document.cookie)" tabindex="0" autofocus=""> // Step 4: When injected into DOM, XSS executes document.body.innerHTML = clean; // alert() fires ``` ### Tested configurations that are vulnerable: | Call Pattern | Vulnerable? | |---|---| | `DOMPurify.sanitize(input)` | YES | | `DOMPurify.sanitize(input, {})` | YES | | `DOMPurify.sanitize(input, { CUSTOM_ELEMENT_HANDLING: null })` | YES | | `DOMPurify.sanitize(input, { CUSTOM_ELEMENT_HANDLING: {} })` | NO (explicit object triggers L591 path) | ## Suggested Fix Change line 590 from: ```javascript CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; ``` To: ```javascript CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null); ``` The `create(null)` function (already used elsewhere in DOMPurify, e.g., in `clone()`) creates an object with no prototype, preventing prototype chain inheritance. ### Alternative application-level mitigation: Applications can protect themselves by always providing an explicit `CUSTOM_ELEMENT_HANDLING` in their config: ```javascript DOMPurify.sanitize(input, { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: null, attributeNameCheck: null } }); ``` ## Timeline - **2026-04-04:** Vulnerability discovered during automated DOMPurify fuzzing research (Fermat project) - **2026-04-04:** Confirmed in Chrome browser with DOMPurify 3.3.3 - **2026-04-04:** Verified distinct from GHSA-cj63-jhhr-wcxv and CVE-2024-45801 - **2026-04-04:** Advisory drafted, responsible disclosure initiated ## Credit https://github.com/trace37labs

Source published
22 Apr 2026 17:31:32 UTC
Source modified
10 Sep 2026 03:51:03 UTC
Observed here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

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

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

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

Publisher rating · MODERATE

ChangeIntel

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

Sources read 1 Nov 15:09 UTC · 275 of 275 readable · 272 fully read · documentation 469/469 current