Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm
Not in CISA KEV

CVE-2026-83618 — @xmldom/xmldom

Fix: xmldom/xmldom@7b2ec67

CVE-2026-83618 is a CWE-91 vulnerability in @xmldom/xmldom. A fix is available for @xmldom/xmldom — see the affected versions and patch details below.

xmldom: requireWellFormed DocType publicId/systemId validation is bypassable via an embedded line terminator

Also known asGHSA-vr34-hp96-76pp
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 15, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-83618.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs46th percentile — riskier than 46% of all scored CVEsHighest risk
0.07%0.40%0.74%1.07%0.6%0.6%Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

2Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@xmldom/xmldomnpm
58.3Mdownloads / week

Description

Summary

An embedded line terminator bypasses the requireWellFormed serializer check for a DocumentType's publicId and systemId. The check was added to fix GHSA-f6ww-3ggp-fr8h; an id whose first line is a valid literal slips past it and is emitted verbatim into the <!DOCTYPE …> declaration, so the markup after the line terminator breaks out into the surrounding document. Callers who enabled requireWellFormed to neutralize DocumentType injection remain exposed.

Details

publicId and systemId are stored as raw values including their surrounding quotes, and the PubidLiteral/SystemLiteral productions include those quotes. The serializer validates them with g.PubidLiteral_match.test(publicId) and g.SystemLiteral_match.test(systemId), where both matchers are reg('^', …, '$') and inherit the m flag from xmldom's shared regexp builder. Under m, $ matches at an interior line terminator, so a value such as "valid pubid"\n"><!ENTITY …> satisfies the matcher on its first line ("valid pubid" is a complete PubidLiteral) and the whole value — including the post-newline breakout — is emitted after PUBLIC/SYSTEM.

Root Cause

  1. A shared regexp builder compiles anchored productions with the m flag.
  2. ^…$ under m are line anchors, not string anchors.
  3. A full-string validator built on such a production (.test()) accepts any string with one conforming line, so a complete, valid literal on the first line passes even though a line terminator and breakout markup follow. PubidChar excluding </> does not prevent it — the breakout is appended after the literal, not embedded inside it.

The triggering line terminators are the ECMAScript LineTerminator set: U+000A, U+000D, U+2028, U+2029.

Affected Versions

Only @xmldom/xmldom 0.9.x is affected. The vulnerable matchers are built by lib/grammar.js's m-flagged reg() builder, and the DocType publicId/systemId requireWellFormed check that consumes them was introduced in 0.9.10 (the GHSA-f6ww-3ggp-fr8h fix); 0.9.10 and 0.9.11 carry it. 0.8.x performs the same requireWellFormed check with inline, non-m regular expressions and is not affected. The unscoped xmldom package has no grammar.js and no requireWellFormed serializer, so there is no check to bypass.

Proof of Concept

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

// publicId: complete literal on line 1, then newline + breakout
const dt = impl.createDocumentType('html', '"valid pubid"\n"><!ENTITY xxe SYSTEM "file:///etc/passwd">', '');
const doc = impl.createDocument(null, 'root', dt);
console.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));
// Observed (no throw):
//   <!DOCTYPE html PUBLIC "valid pubid"
//   "><!ENTITY xxe SYSTEM "file:///etc/passwd">><root/>
// Expected: InvalidStateError (publicId is not a valid PubidLiteral).
// Control: a single-line invalid publicId ("no-surrounding-quotes<>") DOES throw InvalidStateError,
// confirming the check is active and specifically bypassed by the line terminator.

Impact

  • Bypass of the GHSA-f6ww-3ggp-fr8h mitigation. Applications that adopted requireWellFormed: true to neutralize DocumentType injection remain exposed.
  • XML structure injection into the DOCTYPE, including injected markup / entity declarations after the public or system identifier.

Fix Applied

The anchored PubidLiteral/SystemLiteral validators used by the requireWellFormed serializer no longer treat an interior line terminator as satisfying the $ anchor, so a publicId or systemId containing any ECMAScript LineTerminator (U+000A, U+000D, U+2028, U+2029) is rejected with InvalidStateError. Valid single-line identifiers serialize unchanged, and the default serialization path is unaffected.

⚠ 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 serializeToString() call sites and add it.

Proof of Concept - fixed path

const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
const impl = new DOMImplementation();
const dt = impl.createDocumentType('html', '"valid pubid"\n"><!ENTITY xxe SYSTEM "file:///etc/passwd">', '');
const doc = impl.createDocument(null, 'root', dt);

// Default path (requireWellFormed off) — unchanged, still emits verbatim:
console.log(new XMLSerializer().serializeToString(doc));
//   <!DOCTYPE html PUBLIC "valid pubid"
//   "><!ENTITY xxe SYSTEM "file:///etc/passwd">><root/>

// Opt-in path — now throws instead of emitting the breakout:
new XMLSerializer().serializeToString(doc, { requireWellFormed: true });
//   InvalidStateError: DocumentType publicId is not a valid PubidLiteral

Why the default stays verbatim

The W3C DOM Parsing "require well-formed" flag defaults to false, and a browser XMLSerializer emits the DOCTYPE verbatim. Unconditionally throwing on a malformed publicId/systemId would be an unjustified breaking change to the default path, so the fix tightens only the opt-in requireWellFormed validator, matching browser and spec defaults.

Residual limitation

The guarantee holds only for callers that pass { requireWellFormed: true }; the default serialization path still emits publicId/systemId verbatim. publicId and systemId are not validated at creation (createDocumentType) or on direct property assignment (documentType.publicId = …) — the WHATWG DOM specification places no well-formedness constraint on these fields at creation time, so the serializer is the spec-aligned enforcement point.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@xmldom/xmldom≥ 0.9.10&&< 0.9.120.9.12npm install @xmldom/xmldom@0.9.12

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @xmldom/xmldom, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @xmldom/xmldom to 0.9.12 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-83618 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatImportant

A flaw was found in the @xmldom/xmldom JavaScript package. The requireWellFormed serializer improperly validates DocumentType.publicId and DocumentType.systemId properties due to multiline regex flag inheritance in lib/grammar.js. A remote attacker can embed line terminators (U+000A, U+000D, U+2028, U+2029) and…

Workaround published by Red Hat
Sanitize DocumentType.publicId and DocumentType.systemId inputs to strip all line terminators prior to serialization, or disable strict serialization features if untrusted DocumentType properties are processed.
Source: Red Hat security advisory for CVE-2026-83618 (CC BY 4.0)

Frequently Asked Questions

## Summary An embedded line terminator bypasses the `requireWellFormed` serializer check for a `DocumentType`'s publicId and systemId. The check was added to fix GHSA-f6ww-3ggp-fr8h; an id whose first line is a valid literal slips past it and is emitted verbatim into the `<!DOCTYPE …>` declaration, so the markup after the line terminator breaks out into the surrounding document. Callers who enabled `requireWellFormed` to neutralize DocumentType injection remain exposed. ## Details `publicId` and `systemId` are stored as raw values **including their surrounding quotes**, and the `PubidLitera
O3 Security · Impact-Aware SCA

Is CVE-2026-83618 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-83618: @xmldom/xmldom XXE — Fixed in 0.9.12