high

CVE

CVE-2026-83619

CWE

CWE-400, CWE-1333

Affected Surface

  • The npm package @xmldom/xmldom in versions >=0.7.0 and <0.8.15, including the published 0.7.x and 0.8.x lines called out by the upstream advisory
  • Node.js services, CLIs, build tooling, and integration code that call DOMParser.parseFromString() on attacker-controlled XML while pinned to @xmldom/xmldom 0.7.x or 0.8.x
  • Downstream JavaScript and TypeScript applications that bundle the affected parser transitively and expose XML parsing over HTTP, queues, file upload paths, or webhook ingestion

The newest package-ecosystem issue from the last three days is not a maintainer compromise or install hook. It is a parser-level availability bug in @xmldom/xmldom that makes a very small XML document expensive enough to stall a Node.js process.

CVE-2026-83619 was published into NVD on 1 September 2026. The vulnerable path is short, reachable under default parser settings, and easy to reason about from the upstream patch:

attacker-controlled XML
  -> DOMParser.parseFromString()
  -> parse() in lib/sax.js
  -> source.substring(tagStart + 2, end)
  -> .replace(/[ \t\n\r]+$/g, '')
  -> quadratic regex backtracking on malformed end tag
  -> event loop stall

This is an availability bug, not a code execution bug. The reason it still matters for AppSec teams is that it turns routine XML parsing into an attacker-controlled CPU sink before the parser can finish validating the document.

Affected package and fixed version

The affected package is the npm package @xmldom/xmldom.

PackageAffected versionsFixed versionNot affected
@xmldom/xmldom>=0.7.0 <0.8.150.8.150.9.x

Two scope details are worth keeping straight:

  • the vulnerable regex exists on the published 0.7.x and 0.8.x scoped package lines
  • the upstream advisory says the 0.9.x parser rewrote this path and never had the vulnerable regex

The bug lives in one trim operation

The advisory and patch both point to the same line in lib/sax.js. When the parser hits an end tag, it extracts the substring between </ and > and trims trailing whitespace:

var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');

That looks harmless until you consider what the attacker controls. The substring is delimited only by the next >, so an attacker can supply a very long run of whitespace followed by one non-whitespace character:

<r></                                                                 x>

Reduced to its essential shape, the dangerous input is:

"</" + " ".repeat(N) + "x>"

Because the regex is unanchored and global:

/[ \t\n\r]+$/g

the engine keeps trying different start positions for the whitespace run, extends [ \t\n\r]+ toward the end of the string, then fails the $ anchor when it hits the trailing x. That retry pattern grows quadratically with the whitespace length.

The upstream advisory’s proof of concept uses exactly that path:

const { DOMParser } = require('@xmldom/xmldom');
const n = 64 * 1024;
const payload = '<r></' + ' '.repeat(n) + 'x>';
console.time('parse');
new DOMParser().parseFromString(payload, 'text/xml');
console.timeEnd('parse');

Their measured timings are what make this bug operationally relevant:

Whitespace runEnd-to-end parse time on 0.8.13
4 KB5.7 ms
8 KB22.5 ms
16 KB92 ms
32 KB361 ms
64 KB1452 ms

That is not just “slow XML.” It is the classic shape of quadratic work. A parser that should spend near-linear time on a small string instead burns CPU for seconds on a malformed end tag that fits comfortably inside an ordinary request body.

Why the malformed tag is still reachable

This issue does not require an obscure compatibility mode or a non-default option. The path sits under DOMParser.parseFromString() itself. The attacker does not need the document to become valid XML. They only need the parser to attempt the end-tag trim before error handling finishes.

The security consequence is simple:

one request
  -> one parse
  -> one event loop stall
  -> queued requests back up behind the blocked worker

In a single-threaded Node.js service, that can be enough to convert a modest XML endpoint into a practical denial-of-service target.

What the fix changed

The patch does not change the parser architecture. It replaces the vulnerable trim with an anchored expression that matches the whole string once and captures the content up to the last non-whitespace character:

- var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');
+ var tagName = source.substring(tagStart + 2, end).replace(/^([\s\S]*?[^ \t\n\r])?[ \t\n\r]*$/, '$1');

That change is important for two reasons:

  1. it preserves byte-identical trimmed output for normal input
  2. it removes the repeated restart behavior that made the original regex quadratic

The fix commit also added a regression test that compares a hostile malformed end tag against a benign same-size XML string and fails if the ratio stays too high. That is the right kind of test for this class of bug, because it verifies complexity behavior rather than only checking parse correctness.

Why this matters beyond one library

@xmldom/xmldom often shows up in places that do not look like “XML-heavy infrastructure”:

  • webhook receivers that still accept XML from older integrations
  • SAML, SOAP, RSS, SVG, DOCX, or XML-config processing in Node.js services
  • build or import tooling that parses XML project files during CI
  • browser-adjacent server utilities that normalize XML before later application logic

The higher-order lesson is familiar. Parser code does not need memory corruption or eval() to become a real security problem. If a default code path lets the attacker select an expensive algorithmic case, CPU time becomes the resource being exploited.

What to check right now

Start with dependency inventory:

npm ls @xmldom/xmldom
pnpm why @xmldom/xmldom
yarn why @xmldom/xmldom

Then look for direct XML parsing paths in application code:

rg -n "DOMParser|parseFromString\\(|@xmldom/xmldom|text/xml|application/xml" .

If a vulnerable version sits on an internet-facing path, check whether untrusted callers can send XML bodies, uploaded files, webhook payloads, or transformed content that eventually lands in parseFromString().

Response guidance

  1. Upgrade @xmldom/xmldom to 0.8.15 or later immediately, or move to the unaffected 0.9.x line if your compatibility testing allows it.
  2. Rebuild lockfiles and production images so the patched parser actually ships.
  3. Put size limits and request timeouts around XML parsing paths that accept untrusted input.
  4. Treat this as a transitive-dependency issue too, not only a direct-import issue.
  5. If you cannot upgrade quickly, move XML parsing off the hot request path or isolate it behind stricter input limits.

This advisory is a good reminder that application security issues in package ecosystems are not limited to malware or obvious injection sinks. Sometimes the entire bug is one regex at one parse boundary, and that is still enough to hand an attacker control over your service’s CPU budget.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References