chore(deps): update dependency @xmldom/xmldom to v0.9.12 [security] - #3185
Open
renovate[bot] wants to merge 1 commit into
Open
chore(deps): update dependency @xmldom/xmldom to v0.9.12 [security]#3185renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
Contributor
Author
|
|
This PR will trigger no release when merged. |
renovate
Bot
force-pushed
the
renovate-npm-xmldom-xmldom-vulnerability
branch
27 times, most recently
from
September 4, 2026 13:59
8fd2c0a to
2ac39f8
Compare
renovate
Bot
force-pushed
the
renovate-npm-xmldom-xmldom-vulnerability
branch
29 times, most recently
from
September 9, 2026 11:30
e371401 to
ddc14f0
Compare
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:
0.9.10→0.9.12xmldom: XML injection via unsafe CDATA serialization allows attacker-controlled markup insertion
CVE-2026-34601 / GHSA-wh4c-j3r5-mjhp
More information
Details
Summary
@xmldom/xmldomallows attacker-controlled strings containing the CDATA terminator]]>to be inserted into aCDATASectionnode. During serialization,XMLSerializeremitted the CDATA content verbatim without rejecting or safely splitting the terminator. As a result, data intended to remain text-only became active XML markup in the serialized output, enabling XML structureinjection and downstream business-logic manipulation.
The sequence
]]>is not allowed inside CDATA content and must be rejected or safely handled during serialization. (MDN Web Docs)Attack surface
Document.createCDATASection(data)is the most direct entry point, but it is not the only one. The WHATWG DOM spec intentionally does not validate]]>in mutation methods — onlycreateCDATASectioncarries that guard. The following paths therefore also allow]]>to enter a CDATASection node and reach the serializer:CharacterData.appendData()CharacterData.replaceData()CharacterData.insertData().data.textContent(Note: assigning to
.nodeValuedoes not update.datain this implementation — the serializer reads.datadirectly — so.nodeValueis not an exploitable path.)Parse path
Parsing XML that contains a CDATA section is not affected. The SAX parser's non-greedy
CDSectregex stops at the first]]>, so parsed CDATA data never contains the terminator.Impact
If an application uses
xmldomto generate "trusted" XML documents that embed untrusted user input inside CDATA (a common pattern in exports, feeds, SOAP/XML integrations, etc.), an attacker can inject additional XML elements/attributes into the generated document.This can lead to:
<approved>true</approved>,<role>admin</role>, workflow flags, or other security-relevant elements).This issue does not require malformed parsers or browser behavior; it is caused by serialization producing attacker-influenced XML markup.
Root Cause (with file + line numbers)
File:
lib/dom.js1. No validation in
createCDATASectioncreateCDATASection: function (data)accepts any string and appends it directly.2. Unsafe CDATA serialization
Serializer prints CDATA sections as:
without handling
]]>in the data.Because CDATA content is emitted verbatim, an embedded
]]>closes the CDATA section early and the remainder of the attacker-controlled payload is interpreted as markup in the serialized XML.Proof of Concept — Fix A:
createCDATASectionnow throwsOn patched versions, passing
]]>directly tocreateCDATASectionthrowsInvalidCharacterErrorinstead of silently accepting the payload:Expected output on patched versions:
Proof of Concept — Fix B: mutation vector now safe
On patched versions, injecting
]]>via a mutation method (appendData,replaceData,.data =,.textContent =) no longer produces injectable output. The serializer splits the terminator so the result round-trips as safe text:Expected output on patched versions:
Fix Applied
Both mitigations were implemented:
Option A — Strict/spec-aligned: reject
]]>increateCDATASection()Document.createCDATASection(data)now throwsInvalidCharacterError(per the WHATWG DOM spec) whendatacontains]]>. This closes the direct entry point.Code that previously passed a string containing
]]>tocreateCDATASectionand relied on the silent/unsafe behaviour will now receiveInvalidCharacterError. Use a mutation method such asappendDataif you intentionally need]]>in a CDATASection node's data (the serializer split in Option B will keep the output safe).Option B — Defensive serialization: split the terminator during serialization
XMLSerializernow replaces every occurrence of]]>in CDATA section data with the split sequence]]]]><![CDATA[>before emitting. This closes all mutation-vector paths that Option A alone cannot guard, and means the serialized output is always well-formed XML regardless of how]]>entered the node.Update — 2026-04-xx (0.9.10 / 0.8.13)
splitCDATASectionsis deprecatedThe CDATA split behavior introduced as Option B of this fix (replacing
]]>with]]]]><![CDATA[>during serialization) is deprecated as of 0.9.10 / 0.8.13.This release introduces a
requireWellFormedoption onXMLSerializer.serializeToString(). When{ requireWellFormed: true }is passed as the second argument, the serializer throwsInvalidStateErrorif CDATA section data contains]]>— this is the spec-aligned behavior (W3C DOM Parsing and Serialization,require well-formedflag) and the recommended migration path going forward.The split behavior is now controlled by an explicit
splitCDATASectionsoption (defaulttrue, preserving the current behavior). The three serialization behaviors are:|
requireWellFormed|splitCDATASections| Behavior ||---|---|---||false(default) |true(default) | Split]]>→]]]]><![CDATA[>(current behavior, deprecated) ||true| — (ignored) | ThrowInvalidStateError— spec-aligned, recommended |\false|false| Emit verbatim — same as pre-0.9.9 behavior |requireWellFormed: truetakes precedence: the split path is unreachable when it is set.Migration
Replace any reliance on the default split behavior with an explicit opt-in:
No validation is performed on
data. Any string including?>is stored as-is.lib/dom.js— serializer PI case (line 2966):node.datais emitted verbatim. If it contains?>, that sequence terminates the PI in the outputstream and the remainder appears as active XML markup.
Contrast — CDATA (line 2945, patched):
PoC
Minimal (from @tlsbollei report, 2026-04-01)
With re-parse verification (from @tlsbollei report)
Impact
An application that uses the package to build XML from untrusted input can be made to emit attacker-controlled elements outside the intended PI boundary. That allows the attacker to alter the meaning and structure of generated XML documents.
In practice, this can affect any workflow that generates XML and then stores it, forwards it, signs it, or hands it to another parser. Realistic targets include XML-based configuration, policy documents, and message formats where downstream consumers trust the serialized structure.
As noted by @tlsbollei: this is the same delimiter-driven XML injection bug class previously addressed by GHSA-wh4c-j3r5-mjhp for
createCDATASection(). Fixing CDATA while leaving PI creation and PI serialization unguarded leaves the same standards-constrained issue open for another node type.Disclosure
This vulnerability was publicly disclosed at 2026-04-06T11:25:07Z via
xmldom/xmldom#987, which was subsequently closed
without being merged.
Fix Applied
XMLSerializer.serializeToString()now accepts an options object as a second argument. When{ requireWellFormed: true }is passed, the serializer throwsInvalidStateErrorbefore emitting any ProcessingInstruction node whose.datacontains?>. This check applies regardless of how?>entered the node — whether viacreateProcessingInstructiondirectly or a subsequent mutation (.data =,CharacterDatamethods).On
@xmldom/xmldom≥ 0.9.10, the serializer additionally applies the full W3C DOM Parsing §3.2.1.7 checks whenrequireWellFormed: true:InvalidStateErrorif the PI target contains a:character or is an ASCII case-insensitive match for"xml".InvalidStateErrorif the PI data contains characters outside the XML Char production.InvalidStateErrorif the PI data contains?>.On
@xmldom/xmldom≥ 0.8.13 (LTS), only the?>data check (check 3) is applied. The target and XML Char checks are not included in the LTS fix.PoC — fixed path
The guard catches
?>regardless of when it was introduced:Why the default stays verbatim
The W3C DOM Parsing and Serialization spec §3.2.1.3 defines a
require well-formedflag whose default value isfalse. With the flag unset, the spec explicitly permits serializing PI data verbatim. This matches browser behavior: Chrome, Firefox, and Safari all emit?>in PI data verbatim by default without error.Unconditionally throwing would be a behavioral breaking change with no spec justification. The opt-in
requireWellFormed: trueflag allows applications that require injection safety to enable strict mode without breaking existing code.Residual limitation
createProcessingInstruction(target, data)does not validatedataat creation time. The WHATWG DOM spec (§4.5 step 2) mandates anInvalidCharacterErrorwhendatacontains?>; enforcing this check unconditionally at creation time is a breaking change and is deferred to a future breaking release.When the default serialization path is used (without
requireWellFormed: true), PI data containing?>is still emitted verbatim. Applications that do not passrequireWellFormed: trueremain exposed.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
xmldom has XML injection through unvalidated DocumentType serialization
CVE-2026-41674 / GHSA-f6ww-3ggp-fr8h
More information
Details
Summary
The package serializes
DocumentTypenode fields (internalSubset,publicId,systemId) verbatimwithout any escaping or validation. When these fields are set programmatically to attacker-controlled
strings,
XMLSerializer.serializeToStringcan produce output where the DOCTYPE declaration isterminated early and arbitrary markup appears outside it.
Details
DOMImplementation.createDocumentType(qualifiedName, publicId, systemId, internalSubset)validatesonly
qualifiedNameagainst the XML QName production. The remaining three arguments are storedas-is with no validation.
The XMLSerializer emits
DocumentTypenodes as:All fields are pushed into the output buffer verbatim — no escaping, no quoting added.
internalSubsetinjection: The serializer wrapsinternalSubsetwith[and]. A valuecontaining
]>closes the internal subset and the DOCTYPE declaration at the injection point.Any content after
]>ininternalSubsetappears outside the DOCTYPE in the serialized output asraw XML markup. Reported by @TharVid (GHSA-f6ww-3ggp-fr8h). Affected:
@xmldom/xmldom≥ 0.9.0via
createDocumentTypeAPI; 0.8.x only via direct property write.publicIdinjection: The serializer emitspublicIdverbatim afterPUBLICwith noquoting added. A value containing an injected system identifier (e.g.,
"pubid" SYSTEM "evil") breaks the intended quoting context, injecting a fake SYSTEM entryinto the serialized DOCTYPE declaration. Identified during internal security research. Affected:
both branches, all versions back to 0.1.0.
systemIdinjection: The serializer emitssystemIdverbatim. A value containing>terminates the DOCTYPE declaration early; content after
>appears as raw XML markup outsidethe DOCTYPE context. Identified during internal security research. Affected: both branches, all
versions back to 0.1.0.
The parse path is safe: the SAX parser enforces the
PubidLiteralandSystemLiteralgrammarproductions, which exclude the relevant characters, and the internal subset parser only accepts a
subset it can structurally validate. The vulnerability is reachable only through programmatic
createDocumentTypecalls with attacker-controlled arguments.Affected code
lib/dom.js—createDocumentType(lines 898–910):lib/dom.js— serializer DOCTYPE case (lines 2948–2964):PoC
internalSubset injection
publicId quoting context break
systemId injection
Impact
An application that programmatically constructs
DocumentTypenodes from user-controlled data andthen serializes the document can emit a DOCTYPE declaration where the internal subset is closed
early or where injected SYSTEM entities or other declarations appear in the serialized output.
Downstream XML parsers that re-parse the serialized output and expand entities from the injected
DOCTYPE declarations may be susceptible to XXE-class attacks if they enable entity expansion.
Fix Applied
XMLSerializer.serializeToString()now accepts an options object as a second argument. When{ requireWellFormed: true }is passed, the serializer validates theDocumentTypenode'spublicId,systemId, andinternalSubsetfields before emitting the DOCTYPE declaration and throwsInvalidStateErrorif any field contains an injection sequence:publicId: throws if non-empty and does not match the XMLPubidLiteralproduction (XML 1.0 [12])systemId: throws if non-empty and does not match the XMLSystemLiteralproduction (XML 1.0 [11])internalSubset: throws if it contains]>(which closes the internal subset and DOCTYPE declaration early)All three checks apply regardless of how the invalid value entered the node — whether via
createDocumentTypearguments or a subsequent direct property write.PoC — fixed path
The guard also covers post-creation property writes:
Why the default stays verbatim
The W3C DOM Parsing and Serialization spec §3.2.1.3 defines a
require well-formedflag whose default value isfalse. With the flag unset, the spec permits verbatim serialization of DOCTYPE fields. Unconditionally throwing would be a behavioral breaking change with no spec justification. The opt-inrequireWellFormed: trueflag allows applications that require injection safety to enable strict mode without breaking existing deployments.Residual limitation
createDocumentType(qualifiedName, publicId, systemId[, internalSubset])does not validatepublicId,systemId, orinternalSubsetat creation time. This creation-time validation is a breaking change and is deferred to a future breaking release.When the default serialization path is used (without
requireWellFormed: true), all three fields are still emitted verbatim. Applications that do not passrequireWellFormed: trueremain exposed.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
xmldom: Uncontrolled recursion in XML serialization leads to DoS
CVE-2026-41673 / GHSA-2v35-w6hq-6mfw
More information
Details
Summary
Seven recursive traversals in
lib/dom.jsoperate without a depth limit. A sufficiently deeplynested DOM tree causes a
RangeError: Maximum call stack size exceeded, crashing the application.Reported operations:
Node.prototype.normalize()— reported by @praveen-kv (email 2026-04-05) and @KarimTantawey (GHSA-fwmp-8wwc-qhv6, viaDOMParser.parseFromString())XMLSerializer.serializeToString()— reported by @Jvr2022 (GHSA-2v35-w6hq-6mfw) and @KarimTantawey (GHSA-j2hf-fqwf-rrjf)Additionally, discovered in research:
Element.getElementsByTagName()/getElementsByTagNameNS()/getElementsByClassName()/getElementById()Node.cloneNode(true)Document.importNode(node, true)node.textContent(getter)Node.isEqualNode(other)All seven share the same root cause: pure-JavaScript recursive tree traversal with no depth guard.
A single deeply nested document (parsed successfully) triggers any or all of these operations.
Details
Root cause
lib/dom.jsimplements DOM tree traversals as depth-first recursive functions. Each level ofelement nesting adds one JavaScript call frame. The JS engine's call stack is finite; once
exhausted, a
RangeError: Maximum call stack size exceededis thrown. This error may not becaught reliably at stack-exhaustion depths because the catch handler itself requires stack
frames to execute — especially in async scenarios, where an uncaught
RangeErrorinside acallback or promise chain can crash the entire Node.js process.
Parsing a deeply nested document succeeds — the SAX parser in
lib/sax.jsis iterative.The crash occurs during subsequent operations on the parsed DOM.
Node.prototype.normalize()— reported by @praveen-kvlib/dom.js:1296–1308(main):Crash threshold (Node.js 18, default stack): ~10,000 levels.
XMLSerializer.serializeToString()— reported by @Jvr2022lib/dom.js:2790–2974(main):The internal
serializeToStringworker recurses into child nodes at four call sites, eachpassing a
visibleNamespaces.slice()copy. The per-frame allocation causes earlier stackexhaustion than
normalize().Crash threshold (Node.js 18, default stack): ~5,000 levels.
Additional recursive entry points
All five crash at ~10,000 levels on Node.js 18.
_visitNodelib/dom.js:1529getElementsByTagName(),getElementsByTagNameNS(),getElementsByClassName(),getElementById()cloneNode(module fn)lib/dom.js:3037Node.prototype.cloneNode(true)importNode(module fn)lib/dom.js:2975Document.prototype.importNode(node, true)getTextContent(inner fn)lib/dom.js:3130node.textContent(getter)isEqualNodelib/dom.js:1120Node.prototype.isEqualNode(other)Both active branches (
mainandrelease-0.8.x) are identically affected. The unscopedxmldompackage (≤ 0.6.0) carries the same recursive patterns from its initial commit.
Browser behavior
Tested with Chromium 147 (Playwright headless). Chromium's native C++ implementations of all
seven DOM methods are iterative — they traverse the DOM without consuming JS call stack frames.
All seven succeed at depths up to 20,000 without any crash.
When
@xmldom/xmldomis bundled and run in a browser context the same recursive JS code executesunder the browser's V8 stack limit (~12,000–13,000 frames). The crash thresholds are similar to
those observed on Node.js 18 (~5,000 for
serializeToString, ~10,000 for the remaining six).The vulnerability is specific to xmldom's pure-JavaScript recursive implementation, not an
inherent property of the DOM operations.
PoC
normalize()(from @praveen-kv report, 2026-04-05)XMLSerializer.serializeToString()(from GHSA-2v35-w6hq-6mfw)The other methods have been verified using similar pocs.
Impact
Any service that accepts attacker-controlled XML and subsequently calls any of the seven affected
DOM operations can be forced into a reliable denial of service with a single crafted payload.
The immediate result is an uncaught
RangeErrorand failed request processing. In deploymentswhere uncaught exceptions terminate the worker or process, the impact can extend beyond a single
request and disrupt service availability more broadly.
No authentication, special options, or invalid XML is required. A valid, deeply nested XML
document is enough.
Disclosure
The
normalize()vector was publicly disclosed at 2026-04-06T11:25:07Z viaxmldom/xmldom#987 (closed without merge).
serializeToString()and the five additional recursive entry points were not mentioned in that PR.Fix Applied
All seven affected traversals have been converted from recursive to iterative implementations, eliminating call-stack consumption on deep trees.
walkDOMutilityA new
walkDOM(node, context, callbacks)utility is introduced. It traverses the subtree rooted atnodein depth-first order using an explicit JavaScript array as a stack, consuming heap memory instead of call-stack frames.contextis an arbitrary value threaded through the walk — eachcallbacks.enter(node, context)call returns the context to pass to that node's children, enabling per-branch state (e.g. namespace snapshots in the serializer).callbacks.exit(node, context)(optional) is called in post-order after all children have been visited.The following six operations are re-implemented on top of
walkDOM:_visitNodehelpergetElementsByTagName(),getElementsByTagNameNS(),getElementsByClassName(),getElementById()getTextContentinner functionnode.textContentgettercloneNodemodule functionNode.prototype.cloneNode(true)importNodemodule functionDocument.prototype.importNode(node, true)serializeToStringworkerXMLSerializer.prototype.serializeToString(),Node.prototype.toString(),NodeList.prototype.toString()normalizeNode.prototype.normalize()normalizeuseswalkDOMwith anullcontext and anentercallback that merges adjacent Text children of the current node beforewalkDOMreads and queues those children — so the surviving post-merge children are what the walker descends into.Custom iterative loop for
isEqualNodeOne function cannot use
walkDOM:Node.prototype.isEqualNode(other)(0.9.x only; absent from 0.8.x) compares two trees in parallel. It maintains an explicit stack of{node, other}node pairs — one node from each tree — which cannot be expressed withwalkDOM's single-tree visitor.After the fix
All seven entry points succeed on trees of arbitrary depth without throwing
RangeError. The original PoCs still demonstrate the vulnerability on unpatched versions and confirm the fix on patched versions.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
xmldom has XML node injection through unvalidated comment serialization
CVE-2026-41672 / GHSA-j759-j44w-7fr8
More information
Details
Summary
The package allows attacker-controlled comment content to be serialized into XML without validating or neutralizing comment breaking sequences. As a result, an attacker can terminate the comment early and inject arbitrary XML nodes into the serialized output.
Details
The issue is in the DOM construction and serialization flow for comment nodes.
When
createComment(data)is called, the supplied string is stored as comment data through the generic character-data handling path. That content is kept as-is. Later, when the document is serialized, the serializer writes comment nodes by concatenating the XML comment delimiters with the storednode.datavalue directly.That behavior is unsafe because XML comments are a syntax-sensitive context. If attacker-controlled input contains a sequence that closes the comment, the serializer does not preserve it as literal comment text. Instead, it emits output where the remainder of the payload is treated as live XML markup.
This is a real injection bug, not a formatting issue. The serializer already applies context-aware handling in other places, such as escaping text nodes and rewriting unsafe CDATA terminators. Comment content does not receive equivalent treatment. Because of that gap, untrusted data can break out of the comment boundary and modify the structure of the final XML document.
PoC
Impact
An application that uses the package to build XML from untrusted input can be made to emit attacker-controlled elements outside the intended comment boundary. That allows the attacker to alter the meaning and structure of generated XML documents.
In practice, this can affect any workflow that generates XML and then stores it, forwards it, signs it, or hands it to another parser. Realistic targets include XML-based configuration, policy documents, and message formats where downstream consumers trust the serialized structure.
Disclosure
This vulnerability was publicly disclosed at 2026-04-06T11:25:07Z via xmldom/xmldom#987, which was subsequently closed without being merged.
Fix Applied
XMLSerializer.serializeToString()now accepts an options object as a second argument. When{ requireWellFormed: true }is passed, the serializer throwsInvalidStateErrorbefore emitting a Comment node whose.datawould produce malformed XML.On
@xmldom/xmldom≥ 0.9.10, the full W3C DOM Parsing §3.2.1.4 check is applied: throws if.datacontains--anywhere, ends with-, or contains characters outside the XML Char production.On
@xmldom/xmldom≥ 0.8.13 (LTS), only the-->injection sequence is checked. The0.8.xSAX parser accepts comments containing--(without>), so throwing on bare--would break a previously-working round-trip on that branch. The-->check is sufficient to prevent injection.PoC — fixed path
Why the default stays verbatim
The W3C DOM Parsing and Serialization spec §3.2.1.4 defines a
require well-formedflag whose default value isfalse. With the flag unset, the spec explicitly permits serializing ill-formed comment content verbatim — this is also the behavior of browser implementations (Chrome, Firefox, Safari):new XMLSerializer().serializeToString(doc)produces the injection sequence without error in all major browsers.Unconditionally throwing would be a behavioral breaking change with no spec justification. The opt-in
requireWellFormed: trueflag allows applications that require injection safety to enable strict mode without breaking existing deployments.Residual limitation
The fix operates at serialization time only. There is no creation-time check in
createComment— the spec does not require one for comment data. Any path that leads to a Comment node with--in its data (createComment,appendData,.data =, etc.) produces a node that serializes safely only when{ requireWellFormed: true }is passed toserializeToString.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
xmldom: XML fragment injection via invalid EntityReference.nodeName during requireWellFormed serialization
CVE-2026-83610 / GHSA-6gmq-8vp8-gcm6
More information
Details
Summary
An
EntityReferencenode can be created with an invalid, attacker-controlled name throughDocument.createEntityReference(name). When this node is serialized directly with:the invalid
nodeNameis emitted into the serialized XML fragment without validation or escaping.This can produce real XML markup in the serialized output. In the proof of concept below, the serialized fragment contains
<injected/>, and reparsing the fragment creates a realinjectedelement.Details
The issue appears to be in the serialization path for
ENTITY_REFERENCE_NODE.For several other node types,
requireWellFormed: trueperforms specific validation checks before serialization. For example, comments, processing instructions, document types, and some character data cases are checked before being emitted.However, for
ENTITY_REFERENCE_NODE, the serializer appears to emit the node name directly in entity reference form:As a result, if
nodeNamecontains characters that break out of the intended&name;structure, the serializer can emit additional XML markup.For example, an entity reference created with the name:
is serialized as:
When this fragment is later parsed in an XML context,
<injected/>becomes a real element.This is especially surprising when
{ requireWellFormed: true }is used, because applications may reasonably treat this mode as the stricter or safer XML serialization mode.Proof of Concept
Tested with:
Observed output:
Impact
An application that creates an
EntityReferencefrom attacker-controlled input and then serializes that node or XML fragment withrequireWellFormed: truemay produce XML containing attacker-controlled markup.The impact is limited by two observations:
EntityReferencenodes from ordinary XML entity references.EntityReferencenode as an element child is rejected with aHierarchyRequestError.The main affected scenario is applications that directly use
createEntityReference(name)and then serialize the resulting node or fragment.Fix Applied
Two complementary, non-breaking fixes.
(1)
document.createEntityReference(name)rejects an invalidNameat creation, closing the reachable creation vector by default — the opt-in serializer check alone cannot, since a laternodeNamemutation would bypass a creation-only guard.(2) Under
requireWellFormed, the serializer validates theEntityReferencenodeNameas a well-formed XMLNameand throwsInvalidStateErrorwhen it is not; a valid reference still serializes as&name;. Both ship on both maintained versions. TheEntityReference/createEntityReferencedocs note that underrequireWellFormedthenodeNameis validated as an XMLName, and that xmldom does not expand entities. See the XMLNameproduction.Proof of Concept - fixed path
Why the default stays verbatim
The creation-time anchor is applied by default, because it is classified non-breaking. The serializer check, by contrast, stays gated behind
{ requireWellFormed: true }: W3C DOM Parsing's require-well-formed flag defaults tofalse, and the browserXMLSerializeremits thenodeNameverbatim in that default mode, so unconditionally throwing for an ill-formedEntityReference.nodeNamewould be an unjustified breaking change — which is why the default serialization path stays verbatim.Residual limitation
The creation vector is closed by default — the non-breaking creation-time anchor — with no further deferred work. The residual is at serialization: the default path still emits an ill-formed
nodeNameverbatim, because the serializer check is opt-in via{ requireWellFormed: true }.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:NReferences