fix(import): count every zip record against the entry cap

index.size undercounted it: directory records return before the check, and a
repeated filename overwrites the same map key. Either lets an archive carry
far more records than the cap allows, each costing real central-directory
traversal, while the limit believes the archive is nearly empty.
This commit is contained in:
Marco Beretta 2026-07-29 05:14:13 +02:00
parent f9d0c5fbb4
commit fc57538611
No known key found for this signature in database
GPG key ID: D918033D8E74CC11

View file

@ -128,8 +128,20 @@ function indexEntries(
return new Promise((resolve, reject) => {
const index = new Map<string, yauzl.Entry>();
let total = 0;
/** Counted per record, not per indexed entry. `index.size` undercounts:
* directory records return before it is ever consulted, and a repeated
* filename overwrites the same key. Either lets an archive hold millions
* of records that cost real central-directory traversal while the cap
* believes it is nearly empty. */
let records = 0;
zipfile.on('entry', (entry: yauzl.Entry) => {
records += 1;
if (records > options.maxEntries) {
reject(new ZipBombError(`Archive exceeds ${options.maxEntries} entries`));
return;
}
if (/\/$/.test(entry.fileName)) {
zipfile.readEntry();
return;
@ -142,11 +154,6 @@ function indexEntries(
return;
}
if (index.size + 1 > options.maxEntries) {
reject(new ZipBombError(`Archive exceeds ${options.maxEntries} entries`));
return;
}
/** Cheap early reject from the central directory's declared sizes.
* This field is attacker-controlled and not trusted on its own
* `ArchiveTotals` re-checks the real, streamed byte count on every