high

CVE-2026-83608

npm · @xmldom/xmldom

Summary

xmldom: DocType `name` Injection Bypasses requireWellFormed

Severity
high
EPSS
0.3% (p28)
CWE
CWE-91
Also known as
GHSA-27p8-2357-5qqv#@xmldom/xmldom
Published
2026-09-08
Updated
2026-09-08

Advisory details

Summary

The @xmldom/xmldom serializer emits DocumentType.name verbatim into the <!DOCTYPE …> declaration with no well-formedness guard. GHSA-f6ww-3ggp-fr8h (CVE-2026-41674) hardened the serializer's requireWellFormed path for a DocumentType's sibling fields — publicId, systemId, and internalSubset — but it did not add any check for name. A > (or whitespace) in the name terminates the doctype declaration early, letting the remaining characters become sibling markup in the serialized output.

Because requireWellFormed: true — the recommended mitigation for the prior xmldom injection CVEs — performs no validation on the DocType name, this is a bypass of that control, in the same family as the open element-name (GHSA-w2rr-34g9-rvrj) and attribute-name (GHSA-4w3w-2rp5-g8jm) name-injection advisories.

Details

The serializer's DOCUMENT_TYPE_NODE case runs the requireWellFormed block only against publicId, systemId, and internalSubset, then pushes n.name directly into the buffer between the <!DOCTYPE prefix and the closing >:

Enabling write paths

DocumentType.name is a plain, writable own-property, so the enabling vector differs by line:

This is the same structural root cause the sibling name-injection advisories share: the serializer's requireWellFormed path validates content delimiters but no name field, and every name-like field is a plain writable property, so mutation / direct property-write bypasses any creation-time check.

Root Cause

  1. The serializer's requireWellFormed DocType block checks publicId, systemId, and internalSubset (the fields hardened by GHSA-f6ww-3ggp-fr8h) but has no check for name.
  2. DocumentType.name is a plain writable own-property; on 0.8.x and the unscoped package createDocumentType() does not validate it either.
  3. The serializer emits name directly between the doctype delimiters: <!DOCTYPE ${name}…>.

Proof of Concept

Run against @xmldom/xmldom v0.9.10 (commit bb7a085):

const { DOMImplementation, XMLSerializer, DOMParser } = require('@xmldom/xmldom');

const impl = new DOMImplementation();
const serializer = new XMLSerializer();

// 0.9.x createDocumentType validates the name, so overwrite it via direct property write
const dt = impl.createDocumentType('html', '', '');
dt.name = 'html><script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script';
const doc = impl.createDocument(null, 'r', dt);

const output = serializer.serializeToString(doc, { requireWellFormed: true });
console.log(output);
// Output: <!DOCTYPE html><script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script><r/>
//
// requireWellFormed: true did NOT prevent the injection (no exception thrown).
// The injected <script> is well-formed XHTML that a browser would execute.

Confirmed runtime behavior:

A browser reproduction does not apply: browsers keep DocumentType.name readonly, so the direct-write vector cannot be reproduced in a browser DOM. The injection is specific to xmldom exposing name as writable and serializing it without a guard.

Impact

Applications that build a DocumentType node with an attacker-influenced name — via direct property write on any affected line, or via createDocumentType() on 0.8.x and the unscoped package — and serialize the document are vulnerable to XML/markup injection:

Fix Applied

Under requireWellFormed, the serializer validates the DocType name as a well-formed XML Name and throws InvalidStateError when it is not — matching the sibling publicId/systemId/internalSubset checks. Non-breaking and opt-in; ships on both maintained versions. No creation-time change is made: 0.9.x already validates the name at createDocumentType, and the 0.8.x/unscoped creation gap cannot be closed without a breaking change, so it is left unfixed. See the XML Name production.

⚠ Opt-in required. Protection is not automatic. Existing serialization calls remain vulnerable unless { requireWellFormed: true } is explicitly passed. Applications that serialize untrusted DOM content should audit all

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.