CVE-2026-83618 Overview
CVE-2026-83618 is a validation bypass in the @xmldom/xmldom package, a pure JavaScript W3C-standard DOMParser and XMLSerializer module. Versions from 0.9.10 up to but not including 0.9.12 fail to enforce strict serialization of DocumentType.publicId and DocumentType.systemId. Attackers can inject line-break characters and breakout markup into serialized <!DOCTYPE ...> declarations, defeating the mitigation applied for an earlier DocumentType injection advisory. The flaw is categorized as CWE-91: XML Injection and is fixed in @xmldom/xmldom 0.9.12.
Critical Impact
A network-reachable attacker can inject arbitrary markup into serialized XML documents produced with requireWellFormed: true, breaking downstream integrity assumptions in signing, storage, and message-passing pipelines.
Affected Products
- @xmldom/xmldom version 0.9.10
- @xmldom/xmldom version 0.9.11
- Any Node.js or JavaScript application using the affected versions with requireWellFormed: true serialization
Discovery Timeline
- 2026-09-01 - CVE-2026-83618 published to the National Vulnerability Database
- 2026-09-01 - Last updated in the NVD database
- Fix Released - @xmldom/xmldom 0.9.12 published with the corrected regular expression flags
Technical Details for CVE-2026-83618
Vulnerability Analysis
The XMLSerializer in @xmldom/xmldom uses regular expressions generated by the reg() helper in lib/grammar.js to validate the publicId and systemId fields of a DocumentType node. When requireWellFormed: true is set, the serializer relies on the PubidLiteral_match and SystemLiteral_match patterns to reject unsafe values. The reg() helper compiled these patterns with the multiline (m) flag, causing the anchors ^ and $ to match at every line boundary rather than only at the start and end of the input.
An attacker who controls the publicId or systemId value can craft a payload where the first line contains a syntactically valid literal, followed by a line terminator such as U+000A, U+000D, U+2028, or U+2029, and then arbitrary markup. The validator accepts the input because the first line satisfies the pattern, but the entire string is emitted verbatim into the <!DOCTYPE ...> declaration. Downstream consumers then parse the injected content as XML markup.
Root Cause
The root cause is the use of the m regex flag in reg() for validators that must anchor across the full input. Because DocumentType creation and direct property assignment are unvalidated by design, the serializer was the only line of defense, and its anchors did not enforce whole-string matching.
Attack Vector
Exploitation requires that untrusted input flow into DocumentType.publicId or DocumentType.systemId and that the document be serialized with requireWellFormed: true. No authentication or user interaction is required when the vulnerable code path is reachable over the network, for example in XML-processing web services, SAML flows, or SOAP endpoints.
// Security patch in lib/grammar.js: drop the multiline flag from reg()
return isStr ? part : part.source;
})
.join(''),
- UNICODE_SUPPORT ? 'mu' : 'm'
+ UNICODE_SUPPORT ? 'u' : ''
);
}
Source: xmldom commit 7b2ec67
A parallel change in lib/sax.js tightens end-tag name validation so that a valid tag name followed by a line break and trailing content is no longer silently accepted:
- var tagNameMatch = end > 0 && g.reg('^', g.QName_group, g.S_OPT, '$').exec(tagNameRaw);
+ var endTagNameStrict = g.reg('^', g.QName_group, g.S_OPT, '$');
+ var tagNameMatch = end > 0 && endTagNameStrict.exec(tagNameRaw);
if (!tagNameMatch) {
+ var leadingTagNameMatch = end > 0 && g.reg('^', g.QName_group).exec(tagNameRaw);
+ if (isHTML && leadingTagNameMatch) {
+ errorHandler.warning('end tag name contains invalid trailing characters: "' + tagNameRaw + '"');
+ tagNameMatch = leadingTagNameMatch;
+ } else if (
+ leadingTagNameMatch &&
+ new RegExp(endTagNameStrict.source, endTagNameStrict.flags + 'm').test(tagNameRaw)
+ ) {
+ errorHandler.error('end tag name is followed by a line break and trailing content: "' + tagNameRaw + '"');
+ tagNameMatch = leadingTagNameMatch;
+ } else {
+ return errorHandler.fatalError('end tag name contains invalid characters: "' + tagNameRaw + '"');
+ }
}
Source: xmldom commit 7b2ec67
Detection Methods for CVE-2026-83618
Indicators of Compromise
- Serialized XML output containing <!DOCTYPE declarations with embedded line terminators (U+000A, U+000D, U+2028, U+2029) inside PUBLIC or SYSTEM literal fields.
- Unexpected markup, entity declarations, or additional elements appearing after the DOCTYPE line in emitted documents.
- Applications logging XML validation warnings from downstream parsers that received @xmldom/xmldom output.
Detection Strategies
- Perform a software composition analysis (SCA) scan of package.json and package-lock.json files for @xmldom/xmldom versions 0.9.10 or 0.9.11.
- Add regression tests that pass multiline strings into DocumentType.publicId and DocumentType.systemId and assert the serializer rejects them.
- Inspect XML produced by services and pipelines for DOCTYPE declarations that span multiple lines or contain non-literal content.
Monitoring Recommendations
- Alert on outbound XML payloads whose <!DOCTYPE ...> region contains newline characters or characters outside the XML PubidChar set.
- Monitor application logs for parser errors or warnings emitted by consumers of the serialized XML, which can indicate injection attempts.
- Track dependency updates in CI to ensure @xmldom/xmldom remains at or above 0.9.12 across all services.
How to Mitigate CVE-2026-83618
Immediate Actions Required
- Upgrade @xmldom/xmldom to version 0.9.12 or later in all direct and transitive dependencies.
- Audit application code that assigns untrusted values to DocumentType.publicId or DocumentType.systemId and add input sanitization before assignment.
- Rebuild and redeploy any Node.js services, serverless functions, or client bundles that ship the affected package.
Patch Information
The fix is available in xmldom release 0.9.12 and merged via pull request #1071. Full details are provided in GitHub Security Advisory GHSA-vr34-hp96-76pp. The patch drops the m flag from reg() in lib/grammar.js and tightens end-tag name handling in lib/sax.js.
Workarounds
- Reject or strip U+000A, U+000D, U+2028, and U+2029 characters from any input that flows into DocumentType identifiers before parsing or serialization.
- Avoid setting DocumentType.publicId or DocumentType.systemId from untrusted sources when an upgrade is not immediately possible.
- Validate serialized XML output against a strict schema or grammar downstream of @xmldom/xmldom to catch injected markup.
# Update to the fixed version
npm install @xmldom/xmldom@^0.9.12
# Verify installed version across the dependency tree
npm ls @xmldom/xmldom
# Run an audit to confirm no vulnerable versions remain
npm audit --production
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

