Skip to main content

Boteraser | Website and Server Security Solutions

🛡️ CVE-2026-66010 — dompurify

🟢 CVSS 2.0 — Low ⚠️ Exploit Public CWE-184 OSV
2.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

DOMPurify: CUSTOM_ELEMENT_HANDLING bypasses afterSanitizeElements for allowed custom elements.

Summary

There is a possible hook-policy inconsistency in DOMPurify 3.4.11 involving CUSTOM_ELEMENT_HANDLING.

When a custom element is allowed via CUSTOM_ELEMENT_HANDLING.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

```tsx

const _sanitizeDisallowedNode = function (

currentNode: any,

tagName: string

): boolean {

/* Check if we have a custom element to handle */

if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {

if (

CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&

regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)

) {

return false;

}

if (

CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&

CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)

) {

return false;

}

}

```

CUSTOM_ELEMENT_HANDLING is parsed from user configuration at src/purify.ts: line 741~748

```tsx

const customElementHandling =

objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') &&

cfg.CUSTOM_ELEMENT_HANDLING &&

typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object'

? clone(cfg.CUSTOM_ELEMENT_HANDLING)

: create(null);

CUSTOM_ELEMENT_HANDLING = create(null);

```

In particular, tagNameCheck, attributeNameCheck, and allowCustomizedBuiltInElements are copied into the internal CUSTOM_ELEMENT_HANDLING object there.

During element sanitization, _sanitizeElements() checks whether a node is forbidden or not allowlisted at src/purify.ts: line 1805~1814

```tsx

/* Remove element if anything forbids its presence */

if (

FORBID_TAGS[tagName] ||

(!(

EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function &&

EXTRA_ELEMENT_HANDLING.tagCheck(tagName)

) &&

!ALLOWED_TAGS[tagName])

) {

return _sanitizeDisallowedNode(currentNode, tagName);

}

```

If so, it immediately delegates to _sanitizeDisallowedNode(currentNode, tagName) and returns its boolean result.

Inside _sanitizeDisallowedNode(), the custom-element-specific allow path is implemented at src/purify.ts: line 1672~1692

```tsx

const _sanitizeDisallowedNode = function (

currentNode: any,

tagName: string

): boolean {

/* Check if we have a custom element to handle */

if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {

if (

CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&

regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)

) {

return false;

}

if (

CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&

CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)

) {

return false;

}

}

```

If the node is treated as a basic custom element and CUSTOM_ELEMENT_HANDLING.tagNameCheck matches, the function returns false immediately at line 1682 or 1689, meaning “do not remove this node”.

That early return false is significant because control returns directly to _sanitizeElements() via the return _sanitizeDisallowedNode(...) at line 1813. As a result, the later logic in _sanitizeElements() is skipped for that custom element instance, including:

  • the namespace validation at src/purify.ts: line 1816~1826

```tsx

  • Check whether element has a valid namespace.

Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype

nodeType getter rather than instanceof Element, which is realm-

bound and short-circuits to false for any node minted in a different

realm — letting a foreign-realm element with a forbidden namespace

slip past the namespace check entirely. */

const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;

if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {

_forceRemove(currentNode);

return true;

}

```

  • the fallback-tag mXSS check at src/purify.ts: line 1828~1837

```tsx

/* Make sure that older browsers don't get fallback-tag mXSS */

if (

(tagName === 'noscript' ||

tagName === 'noembed' ||

tagName === 'noframes') &&

regExpTest(EXPRESSIONS.FALLBACK_

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is high, an attacker needs no privileges on the target. A user must actively cooperate. Rated impact: confidentiality none, integrity none, availability none.

CVSS metrics in full

The score comes from this vector: CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

  • Attack vector: Network — reachable from anywhere that can route to the service.
  • Attack complexity: High — the attacker first has to win a race, learn a secret or otherwise prepare the target.
  • Attack requirements: None — no deployment-specific condition has to hold.
  • Privileges required: None — an unauthenticated stranger can try it.
  • User interaction: Active — the victim has to cooperate with the attack.
  • Confidentiality impact: None.
  • Integrity impact: None.
  • Availability impact: None.

Weakness class

CVE-2026-66010 is classified as CWE-184: Incomplete List of Disallowed Inputs. The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.

Affected software

CVE-2026-66010 is recorded against 1 package.

  • dompurify

Timeline and source

Published on 21 July 2026 and last revised on 6 August 2026. A public exploit is known to exist, which raises the urgency of patching considerably. Record sourced from OSV.

References

github.com (Web)
github.com (Web)
github.com (Web)
github.com (Package)
github.com (Web)

Other advisories for this package

dompurify has other advisories on record. If you are patching this one, these are worth checking on the same host:

Same weakness in other software

These advisories are the same class of weakness (CWE-184: Incomplete List of Disallowed Inputs) in other software:

Details

Severity LOW
CVSS Score 2.0
CVSS Vector CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N
CWE CWE-184
Public Exploit ⚠️ Yes
Source OSV
Published 2026-07-21
Updated 2026-08-20
Modified 2026-08-06
Fix URL N/A

Affected Packages

Software From version Fixed in
dompurify

Exploit Protection

Are you running dompurify?

CVE-2026-66010 carries CVSS 2.0 Low rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-66010 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesCVECVE 2026