GitHub Security Advisory: GHSA-f6ww-3ggp-fr8h
Package: @xmldom/xmldom (NPM)
Severity: HIGH (CVSS 0.0)
CWE: CWE-91: XML Injection (aka Blind XPath Injection)
Vulnerable versions: >= 0.9.0, < 0.9.10
Patched in: 0.9.10

Summary: xmldom has XML injection through unvalidated DocumentType serialization

Details:
## Summary

The package serializes `DocumentType` node fields (`internalSubset`, `publicId`, `systemId`) verbatim
without any escaping or validation. When these fields are set programmatically to attacker-controlled
strings, `XMLSerializer.serializeToString` can produce output where the DOCTYPE declaration is
terminated early and arbitrary markup appears outside it.

---

## Details

`DOMImplementation.createDocumentType(qualifiedName, publicId, systemId, internalSubset)` validates
only `qualifiedName` against the XML QName production. The remaining three arguments are stored
as-is with no validation.

The XMLSerializer emits `DocumentType` nodes as:

```
<!DOCTYPE name[ PUBLIC pubid][ SYSTEM sysid][ [internalSubset]]>
```

All fields are pushed into the output buffer verbatim — no escaping, no quoting added.

**`internalSubset` injection:** The serializer wraps `internalSubset` with ` [` and `]`. A value
containing `]>` closes the internal subset and the DOCTYPE declaration at the injection point.
Any content after `]>` in `internalSubset` appears outside the DOCTYPE in the serialized output as
raw XML markup. Reported by @TharVid (GHSA-f6ww-3ggp-fr8h). Affected: `@xmldom/xmldom` ≥ 0.9.0
via `createDocumentType` API; 0.8.x only via direct property write.

**`publicId` injection:** The serializer emits `publicId` verbatim after `PUBLIC` with no
quoting added. A value containing an injected system identifier (e.g.,
`"pubid" SYSTEM "evil"`) breaks the intended quoting context, injecting a fake SYSTEM entry
into the serialized DOCTYPE declaration. Identified during internal security research. Affected:
both branches, all versions back to 0.1.0.

**`systemId` injection:** The serializer emits `systemId` verbatim. A value containing `>`
terminates the DOCTYPE declaration early; content after `>` appears as raw XML markup outside
the 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 `PubidLiteral` and `SystemLiteral` grammar
productions, 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
`createDocumentType` calls with attacker-controlled arguments.

---

## Affected code

**`lib/dom.js` — `createDocumentType` (lines 898–910):**

```js
createDocumentType: function (qualifiedName, publicId, systemId, internalSubset) {
    validateQualifiedName(qualifiedName);          // only qualifiedName is validated
    var node = new DocumentType(PDC);
    node.name = qualifiedName;
    node.nodeName = qualifiedName;
    node.publicId = publicId || '';               // stored verbatim
    node.systemId = systemId || '';               // stored verbatim
    node.internalSubset = internalSubset || '';   // stored verbatim
    node.childNodes = new NodeList();
    return node;
},
```

**`lib/dom.js` — serializer DOCTYPE case (