Skip to content

Commit 3abdca9

Browse files
committed
support maxEntityCount
1 parent f95852a commit 3abdca9

File tree

5 files changed

+28
-1
lines changed

5 files changed

+28
-1
lines changed

docs/v4/5.Entities.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const parser = new XMLParser({
6060
maxEntitySize: 10000, // Max 10KB per entity definition
6161
maxTotalExpansions: 1000, // Max 1000 entity replacements total
6262
maxExpandedLength: 100000 // Max 100KB total expanded content
63+
maxEntityCount: 100 // Max 100 entities allowed
6364
}
6465
});
6566
```
@@ -90,6 +91,7 @@ FXP includes multiple defense layers against entity expansion attacks:
9091
2. **Expansion Count Limit:** Prevents too many entity replacements
9192
3. **Total Size Limit:** Prevents output from growing too large
9293
4. **Parameter Entity Blocking:** Entities containing `&` are automatically ignored
94+
5. **Entity Count Limit:** Prevents too many entities from being defined
9395

9496
These protections defend against:
9597
- Billion Laughs attack (exponential entity expansion)

lib/fxp.d.cts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ type ProcessEntitiesOptions = {
3434
*/
3535
maxExpandedLength?: number;
3636

37+
/**
38+
* Maximum number of entities allowed in the XML
39+
*
40+
* Defaults to `100`
41+
*/
42+
maxEntityCount?: number;
43+
3744
/**
3845
* Array of tag names where entity replacement is allowed.
3946
* If null, entities are replaced in all tags.

src/fxp.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export type ProcessEntitiesOptions = {
3434
*/
3535
maxExpandedLength?: number;
3636

37+
/**
38+
* Maximum number of entities allowed in the XML
39+
*
40+
* Defaults to `100`
41+
*/
42+
maxEntityCount?: number;
43+
3744
/**
3845
* Array of tag names where entity replacement is allowed.
3946
* If null, entities are replaced in all tags.

src/xmlparser/DocTypeReader.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ export default class DocTypeReader {
77
}
88

99
readDocType(xmlData, i) {
10-
1110
const entities = Object.create(null);
11+
let entityCount = 0;
12+
1213
if (xmlData[i + 3] === 'O' &&
1314
xmlData[i + 4] === 'C' &&
1415
xmlData[i + 5] === 'T' &&
@@ -26,11 +27,19 @@ export default class DocTypeReader {
2627
let entityName, val;
2728
[entityName, val, i] = this.readEntityExp(xmlData, i + 1, this.suppressValidationErr);
2829
if (val.indexOf("&") === -1) { //Parameter entities are not supported
30+
if (this.options.enabled !== false &&
31+
this.options.maxEntityCount &&
32+
entityCount >= this.options.maxEntityCount) {
33+
throw new Error(
34+
`Entity count (${entityCount + 1}) exceeds maximum allowed (${this.options.maxEntityCount})`
35+
);
36+
}
2937
const escaped = entityName.replace(/[.\-+*:]/g, '\\.');
3038
entities[entityName] = {
3139
regx: RegExp(`&${escaped};`, "g"),
3240
val: val
3341
};
42+
entityCount++;
3443
}
3544
}
3645
else if (hasBody && hasSeq(xmlData, "!ELEMENT", i)) {

src/xmlparser/OptionsBuilder.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function normalizeProcessEntities(value) {
5656
maxExpansionDepth: 10,
5757
maxTotalExpansions: 1000,
5858
maxExpandedLength: 100000,
59+
maxEntityCount: 100,
5960
allowedTags: null,
6061
tagFilter: null
6162
};
@@ -69,6 +70,7 @@ function normalizeProcessEntities(value) {
6970
maxExpansionDepth: value.maxExpansionDepth ?? 10,
7071
maxTotalExpansions: value.maxTotalExpansions ?? 1000,
7172
maxExpandedLength: value.maxExpandedLength ?? 100000,
73+
maxEntityCount: value.maxEntityCount ?? 100,
7274
allowedTags: value.allowedTags ?? null,
7375
tagFilter: value.tagFilter ?? null
7476
};

0 commit comments

Comments
 (0)