Repository navigation
chore(deps): update dependency dompurify to v3.4.16 [security] (stable33) - #3438
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
3.4.15→3.4.16DOMPurify:
CUSTOM_ELEMENT_HANDLINGbypassesafterSanitizeElementsfor allowed custom elements.CVE-2026-66010 / GHSA-c2j3-45gr-mqc4
More information
Details
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 throughafterSanitizeElementsin the same way as a normal element. As a result, an application that relies onafterSanitizeElementsas 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~1691CUSTOM_ELEMENT_HANDLINGis parsed from user configuration atsrc/purify.ts: line 741~748In particular,
tagNameCheck,attributeNameCheck, andallowCustomizedBuiltInElementsare copied into the internalCUSTOM_ELEMENT_HANDLINGobject there.During element sanitization,
_sanitizeElements()checks whether a node is forbidden or not allowlisted atsrc/purify.ts: line 1805~1814If so, it immediately delegates to
_sanitizeDisallowedNode(currentNode, tagName)and returns its boolean result.Inside
_sanitizeDisallowedNode(), the custom-element-specific allow path is implemented atsrc/purify.ts: line 1672~1692If the node is treated as a basic custom element and
CUSTOM_ELEMENT_HANDLING.tagNameCheckmatches, the function returnsfalseimmediately at line 1682 or 1689, meaning “do not remove this node”.That early
return falseis significant because control returns directly to_sanitizeElements()via thereturn _sanitizeDisallowedNode(...)at line 1813. As a result, the later logic in_sanitizeElements()is skipped for that custom element instance, including:src/purify.ts: line 1816~1826src/purify.ts: line 1828~1837afterSanitizeElementshook dispatch atsrc/purify.ts: line 1850~1851.In other words, a normal allowlisted element continues through
_sanitizeElements()and reacheshooks.afterSanitizeElements, but a disallowed-by-default element that is revived by theCUSTOM_ELEMENT_HANDLING.tagNameCheckpath does not. This creates a policy inconsistency: an application that relies onafterSanitizeElementsto remove an attribute from all elements will observe that the policy is applied to normal elements but not to custom elements allowed throughCUSTOM_ELEMENT_HANDLING.In the PoC, the application hook removes
data-biofrom ordinary elements, but the same attribute remains on<x-bio>because the custom-element keep path bypassesafterSanitizeElements. The attribute itself is inert at sanitize time and DOMPurify is not directly allowing executable SVG/HTML through. The security impact appears when the application-defined custom element later reads the preserveddata-biovalue inconnectedCallback()and writes it toinnerHTML, turning the preserved attribute into a second-order XSS gadget.PoC
Reproduced on DOMPurify 3.4.11.
Steps
poc.html.divcontrol losesdata-bio, while the allowed custom element keeps it.connectedCallback()runs, the candidate payload is reinserted into the DOM and executes through the custom element’s own sink.HTML PoC
Expected result
This is output of HTML PoC.
Impact
This does not appear to affect DOMPurify’s default configuration as a direct sanitizer bypass.
The impact is limited to applications that:
CUSTOM_ELEMENT_HANDLING,afterSanitizeElementsas a security policy layer,innerHTMLor another HTML sink.In that situation, the behavior can become a second-order XSS gadget because a security-relevant attribute is removed from normal elements but remains on allowed custom elements.
Possible fixes or mitigations might include
afterSanitizeElementsCUSTOM_ELEMENT_HANDLINGmay not participate in the same post-element hook flow as normal allowlisted elements.Severity
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
DOMPurify: IN_PLACE hook removal leaves a detached subtree executable, causing XSS
GHSA-55q2-fjhq-7xh7
More information
Details
Summary
During
IN_PLACEsanitization, a hook that removes an element can leave that element's detached descendants executable. A descendant image can retain its attacker-providedonloadhandler and fire aftersanitize()returns, even though the returned root is clean and the image remains disconnected from the document.Details
In DOMPurify 3.4.12,
_sanitizeElements()insrc/purify.ts:1862-1904runs thebeforeSanitizeElementsoruponSanitizeElementhook 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-walkIN_PLACEneutralization 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 de-arms the same queued event; only the hook-detachment early return skips the existing subtree neutralization.
PoC
Load the published
dompurify@3.4.12dist/purify.jsbefore this script in Chromium:sanitize()returns with no handler execution and the returned root contains only the safediv. After the event loop advances, the original image remains disconnected but its retainedonloadchanges the page toXSS after sanitize.As the claim-matched control, use the same detached input with
ALLOWED_TAGS: ['div', '#text']and no hook. DOMPurify's ordinary removal path removes the original image's handler, the returned root is still<div>safe</div>, and the marker does not fire.Impact
In an application that uses
IN_PLACEwith the documented element-removal hook pattern, an attacker who can supply HTML can execute JavaScript in the integrating application's origin after the application sanitizes and renders that content.The required non-default configuration is
IN_PLACEplus a hook that removes a containing element. The hook does not add or approve the event handler, and the dirty root never needs to be connected before sanitization.Suggested fix
Reuse the existing
_neutralizeSubtree(currentNode)helper before returning from both hook-detachment branches in_sanitizeElements(). Add regressions forbeforeSanitizeElementsanduponSanitizeElementthat retain a reference to a descendant resource element and verify that its event handler is removed after the hook detaches its ancestor.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
DOMPurify: IN_PLACE returns a force-removed rawtext root whose text carries attacker markup — pure HTML reparse executes
GHSA-6688-9rhm-gjv2
More information
Details
Environment
DOMPurify.sanitize(node, { IN_PLACE: true })on a Node input;SAFE_FOR_XMLat its default (true)Summary
The 3.4.9 fix for the IN_PLACE detached-root class added two protections on the IN_PLACE return path: a fail-closed
TypeErrorin_forceRemovewhen a node selected for removal cannot be detached, and a_neutralizeSubtreepass (dist/purify.jsline 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 (theTypeErrorguard is not reached),_neutralizeSubtreestrips nothing (there are no attributes), and the IN_PLACE 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 payload — no foreign-content context required.The same Node input sanitized without
IN_PLACEreturns an empty result: the only difference is the IN_PLACE return path handing the killed node back.Steps to reproduce
Observed on 3.4.15: one node, one call — the
onclickattribute is neutralized while the text payload (</style><img src=x onerror=1>) survives verbatim; serializing and re-parsing the returned node in plain HTML context materializes theimgwith the liveonerrorhandler.Contrast on the same Node input without
IN_PLACE:RETURN_DOM: true→<body></body>;RETURN_DOM_FRAGMENT: true→ 0 children — the payload is fully sanitized away. The only difference is the IN_PLACE return path.Contrast on the removal trigger:
SAFE_FOR_XML: false→ the node is not removed (detached stays false); plain CSS text → not removed. The removal is gated by the mXSS text probes and happens specifically because the serialized node would re-open tags on reparse.Root cause
_isUnsafeNode(dist/purify.js3.4.15, lines 1700–1714) removes nodes whose literal text would re-open tags on reparse — shape (b) in the source comment is "text-only content that already carries the element's OWN end tag", detected by theLITERAL_TEXT_CLOSEprobe (line 385) alongside theELEMENT_MARKUP_PROBE(line 339) rules._forceRemove(line 1122) records the node inDOMPurify.removed({element}) and detaches it. The removal is intentional: the upstream comment states these shapes are removed because the literal serializer emits them verbatim for the HTML parser to re-open.The IN_PLACE exit then hands the force-removed root back to the caller — the very node whose removal
DOMPurify.removedjust recorded (verified:DOMPurify.removed.some(e => e.element === root)istrueon the returned instance). The 3.4.9_neutralizeSubtreepass (line 1336) addresses only the attribute form — its own docstring: "walks a removed subtree and strips every attribute" (purpose: cancel queued resource events). Rawtext text content is out of its scope, so the removal that was performed specifically to prevent reparse is undone by returning the node: you removed it to stop the reparse, then returned it.Differential (one node, one call, same removal path): the
onclickattribute is neutralized by the existing pass while the text payload survives verbatim — the attribute axis is covered, the text axis is the gap.Impact
Identical blast radius to the published IN_PLACE family: an application that sanitizes a Node in
IN_PLACEmode and re-inserts (or serializes and then re-inserts) the result materializes attacker markup in plain HTML context: script execution in the page. Moving the returned node viaappendChildalone is safe; the round trip through serialization is what fires the payload. No foreign-content context is required with the close-tag payload.Affected versions
_neutralizeSubtreeand the IN_PLACE return path are present in 3.4.9–3.4.14; releases before 3.4.9 predate the fix entirely (unconditional return; individual pre-3.4.9 releases not dynamically tested).<= 3.4.15(current at time of writing).Suggested remediation
Primary (root-cause, covers every form): at the IN_PLACE exit, check whether the returned root was recorded during sanitization —
DOMPurify.removed.some(e => e.element === root)— and fail closed: throw the sameTypeErrorstyle used by the 3.4.9 detach guard ("a node selected for removal could not be safely returned; refusing to sanitize in place"), or returnnull. This is consistent with the existing fail-closed design and covers all present and future root-kill reasons in one check.Secondary (form-specific): extend
_neutralizeSubtreeto neutralize text content of rawtext descendants — the elements inLITERAL_TEXT_ELEMENT_NAMES(style,script,xmp,iframe,noembed,noframes,plaintext,noscript) — by rewritingtextContentto a defanged form, matching the probe coverage of_isUnsafeNode/LITERAL_TEXT_CLOSE.A regression test asserting that a force-removed rawtext root comes back with no
/<[/\w!]/match intextContent(and is not returned at all under the primary fix) prevents re-introduction.Prior art / differentiation
styleelement killed by the text probe._neutralizeSubtree's attribute stripping and is not that advisory.Applicability scope (stated up front)
The payload materializes when the application serializes and re-parses the sanitizer output (
innerHTMLassignment, template rendering, markdown/HTML round trips) or otherwise consumes the returned node's markup. Moving the returned node viaappendChildalone does not trigger it. Applications that pass live, connected attacker trees intoIN_PLACEare explicitly warned against by upstream's own source comment; this report concerns the serialize-and-reinsert consumption pattern that the IN_PLACE mode exists to serve.Severity
Low
References
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
cure53/DOMPurify (dompurify)
v3.4.16: DOMPurify 3.4.16Compare Source
IN_PLACEnode removal when working with hooks, thanks @manus-piIN_PLACEsanitization and raw-text roots, thanks @h-t-mrolluptorolldownbecause performance, thanks @ssi02014Configuration
📅 Schedule: (in timezone Europe/Berlin)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.