Coordinated disclosureCVE-2026-73569 · GHSA-8r6m-32jq-jx6qfast-xml-parser · npm8.7 High

Proof/CVEs & advisories/CVE-2026-73569

CVE.10 — Published record

The limit worked. The counter restarted.

fast-xml-parser enforces maxTotalExpansions and maxExpandedLength correctly — and it accepted a second DOCTYPE mid-document, which zeroed both counters. One ceiling became as many ceilings as the attacker wanted.

CVECVE-2026-73569
AdvisoryGHSA-8r6m-32jq-jx6q
Packagefast-xml-parser
Ecosystemnpm · Node.js
Affected≥5.9.3 <5.10.1
Fixed5.10.1
Severity8.7 High · CVSS 4.0
ReporterCharles Vosburgh
Expansion accounting must be scoped to the whole document. Attacker-controlled syntax inside that document must never reset it.
Weakness
CWE-776
Trigger
repeated DOCTYPE
Control
single-declaration limit holds
Outcome
2× the configured ceiling
Public-safety boundary.The proof is public with GHSA-8r6m-32jq-jx6q and is deliberately bounded — it demonstrates limit bypass without attempting to exhaust a machine. No process-exhaustion or destructive stress test is required to validate the flaw.

A parser limit only works if the counter’s lifetime matches the security boundary it claims to protect. Here the intended boundary is a single call to XMLParser.parse().

This is a resource-control bypass. It does not depend on external entities, local file access, session state, or a classic recursive “Billion Laughs” construction.

Expansion accounting and declaration state must be scoped to the entire XML document and must not be reset by attacker-controlled syntax within that document.

Resetting the decoder and expansion counters at the beginning of a parse is correct:

Correct resetstart of parse
this.matcher.reset();
this.entityDecoder.reset();

this.entityExpansionCount = 0;
this.currentExpandedLength = 0;

In affected source, the XML scanner accepts every <!DOCTYPE token it encounters and hands the newly parsed declarations to the decoder:

Scanner accepts repeatsfast-xml-parser
} else if (c1 === 33
  && xmlData.charCodeAt(i + 2) === 68) { //'!D'
  const result = docTypeReader.readDocType(xmlData, i);
  this.entityDecoder.addInputEntities(result.entities);
  i = result.i;
}

And in the relevant @nodable/entities implementation, addInputEntities() zeroes both document counters:

Decoder resets accounting@nodable/entities
addInputEntities(map) {
  this._totalExpansions = 0;
  this._expandedLength = 0;
  // replace entity map
}

The decode path still increments and checks those counters. The security checks are present and functional; the flaw is that their lifetime can be reset from inside the attacker-controlled document.

A small validation configuration used maxTotalExpansions: 10 and maxExpandedLength: 1000. Three documents isolate the behaviour.

Allowed control

One declaration, at the limit

Ten 100-character expansions stay within the 1,000-character ceiling and are accepted.

Negative control

One declaration, over the limit

Twenty references exceed the ceiling and the parser rejects the aggregate expansion, as designed.

Bypass candidate

Split around a second DOCTYPE

The same twenty references produce 2,000 expanded characters under a configured limit of 1,000.

CONFIG maxTotalExpansions=10 maxExpandedLength=1000 CONTROL single DOCTYPE, 10 refs → accepted (1000 chars) CONTROL single DOCTYPE, 20 refs → REJECTED (limit works) BYPASS 20 refs split around a 2nd DOCTYPE EXPANDED 2000 chars under a 1000-char configured ceiling 5.10.1 repeated declaration rejected: PASS

The differential is what makes the claim precise: the expansion limit itself is working. The bypass depends specifically on the repeated-declaration reset.

Two components held incompatible assumptions about state lifetime. fast-xml-parser allowed more than one DOCTYPE during a single parse; @nodable/entities treated each declaration as a fresh accounting scope. Combined, they converted a document-wide limit into a per-declaration limit controlled by the attacker.

01 · Declare

First DOCTYPE

Entities installed; expansion begins under the configured budget.

02 · Approach

Counters near ceiling

Accounting is working exactly as documented.

03 · Reset

Second DOCTYPE

addInputEntities() zeroes total-expansion and length counters.

04 · Continue

Fresh budget

Expansion resumes; document-wide limit is bypassed.

The demonstrated impact is deterministic defeat of document-wide entity-expansion accounting. An application parsing larger attacker-controlled XML synchronously can perform more entity-replacement work than its configured limits were intended to allow.

The published advisory identifies the practical consequences as excessive CPU use, event-loop blocking, memory pressure or exhaustion, and possible process termination. This page does not claim memory corruption, arbitrary code execution, file disclosure or persistence.

High
Published advisory severity
8.7
CVSS 4.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

fast-xml-parser is a library, not a network service. Remote reachability requires a consuming application to feed remote XML into the affected parser configuration.

The SecHive research loop

Prove the limit works before claiming it can be bypassed.

A resource-limit finding is easy to overstate. The allowed control and the rejecting control had to run first — only then does a document that doubles the ceiling mean anything.

SK.01

Source mapping

Follow the expansion counters from parse entry through the decoder that owns them.

SK.02

Lifetime hypothesis

Ask which grammar events can reset state that represents work already done.

SK.03

Bounded construction

Build three documents — allowed, rejected, split — that differ only in declaration placement.

SK.04

Skeptic gate

Refuse to expand the affected range beyond releases actually executed; record adjacent results honestly.

HUMAN

Promotion and disclosure

Keep the reproduction bounded and separate the DoS claim from stronger primitives.

ScopeValueBasis
Public advisory range≥ 5.9.3, < 5.10.1publisher’s stated range
Verified fixed5.10.1repeated declaration rejected
Exact releases reproduced5.9.3, 5.10.02,000 chars under a 1,000-char ceiling
Adjacent behaviour observed5.9.2, 5.6.0underlying behaviour also present

Those adjacent results matter for accuracy: 5.9.3 should not be described as a source-proven introduction point even though it remains the lower bound in the publisher’s advisory. Releases 5.7.0 through 5.9.1 were not all runtime-tested, so this page does not replace the published range.

Public history points to commit 169fbf94699087087012e4ed68f8c2b30dd948f3 as the apparent introduction of the vulnerable @nodable/entities integration. The fixing change is 4e546e03987662de5495d050b5fba26bea65383f, included in 5.10.1.

Worth checking separately. During bounded checks, XMLValidator could still report the repeated-declaration input as valid while XMLParser rejected it. Applications that use validation as a security precondition before parsing should account for that difference rather than assume the two agree.

Upgrade to fast-xml-parser 5.10.1 or later. The fix rejects repeated declarations in the parser path before the decoder can reset the counters.

For parser implementers, keep document-scoped security state outside helpers whose lifecycle can be reset by grammar events. A declaration parser may update the entity map; it should not be able to reset resource counters representing work already performed in the same document.

Regression coverage should include:

  1. one allowed declaration at the limit;
  2. one declaration exceeding the limit;
  3. multiple declarations split around references;
  4. repeated declarations with zero entities;
  5. parser and validator agreement on declaration legality;
  6. assertions that counters never decrease during one parse.
2026-07-16
GitHub Security Advisory published.
2026-08-13
CVE-2026-73569 published.
2026-08-26
Public PoC and bounded release controls reviewed.
  1. GHSA-8r6m-32jq-jx6q — fast-xml-parser advisory and public PoC
  2. CVE-2026-73569 record
  3. GitHub Advisory Database record

Credit: Discovered and reported by Charles Vosburgh. Research was AI-assisted through source mapping, hypothesis generation, release comparison and evidence organization; final validation, disclosure coordination, severity calibration and publication review remained human.