Proof/CVEs & advisories/CVE-2026-73569
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.
DOCTYPEA 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:
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:
} 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:
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.
One declaration, at the limit
Ten 100-character expansions stay within the 1,000-character ceiling and are accepted.
One declaration, over the limit
Twenty references exceed the ceiling and the parser rejects the aggregate expansion, as designed.
Split around a second DOCTYPE
The same twenty references produce 2,000 expanded characters under a configured limit of 1,000.
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.
First DOCTYPE
Entities installed; expansion begins under the configured budget.
Counters near ceiling
Accounting is working exactly as documented.
Second DOCTYPE
addInputEntities() zeroes total-expansion and length counters.
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.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:Nfast-xml-parser is a library, not a network service. Remote reachability requires a consuming application to feed remote XML into the affected parser configuration.
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.
Source mapping
Follow the expansion counters from parse entry through the decoder that owns them.
Lifetime hypothesis
Ask which grammar events can reset state that represents work already done.
Bounded construction
Build three documents — allowed, rejected, split — that differ only in declaration placement.
Skeptic gate
Refuse to expand the affected range beyond releases actually executed; record adjacent results honestly.
Promotion and disclosure
Keep the reproduction bounded and separate the DoS claim from stronger primitives.
| Scope | Value | Basis |
|---|---|---|
| Public advisory range | ≥ 5.9.3, < 5.10.1 | publisher’s stated range |
| Verified fixed | 5.10.1 | repeated declaration rejected |
| Exact releases reproduced | 5.9.3, 5.10.0 | 2,000 chars under a 1,000-char ceiling |
| Adjacent behaviour observed | 5.9.2, 5.6.0 | underlying 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.
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:
- one allowed declaration at the limit;
- one declaration exceeding the limit;
- multiple declarations split around references;
- repeated declarations with zero entities;
- parser and validator agreement on declaration legality;
- assertions that counters never decrease during one parse.
- GHSA-8r6m-32jq-jx6q — fast-xml-parser advisory and public PoC
- CVE-2026-73569 record
- 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.