actualbudget/actual · error · UnsafeZipError
Zip archive's total uncompressed size exceeds maximum of ${m
Error message
Zip archive's total uncompressed size exceeds maximum of ${maxTotalUncompressedSize} bytes What it means
The filter callback accumulates each entry's uncompressed size and throws UnsafeZipError with zipReason 'total-size' once the running total exceeds maxTotalUncompressedSize. This caps the aggregate decompressed output of the whole archive, complementing the per-entry limit.
Source
Thrown at packages/loot-core/src/server/util/zip.ts:79
return unzipSync(data, {
filter(file) {
assertSafeEntryName(file.name);
if (file.originalSize > maxEntrySize) {
throw new UnsafeZipError(
`Zip entry "${file.name}" exceeds maximum size of ${maxEntrySize} bytes`,
{
zipReason: 'entry-size',
entryName: file.name,
maxSize: maxEntrySize,
},
);
}
totalUncompressedSize += file.originalSize;
if (totalUncompressedSize > maxTotalUncompressedSize) {
throw new UnsafeZipError(
`Zip archive's total uncompressed size exceeds maximum of ${maxTotalUncompressedSize} bytes`,
{ zipReason: 'total-size', maxSize: maxTotalUncompressedSize },
);
}
const normalized = file.name.toLowerCase();
if (seen.has(normalized)) {
throw new UnsafeZipError(
`Zip archive contains a duplicate entry: ${file.name}`,
{ zipReason: 'duplicate-entry', entryName: file.name },
);
}
seen.add(normalized);
return true;
},
});
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Prune old attachments or split the backup into smaller archives to reduce total uncompressed size.
- Raise maxTotalUncompressedSize in the safeUnzip options if your deployment has the memory/disk for it.
- Track cumulative extraction size in your own pipeline and stream entries to disk instead of holding them in memory.
- On untrusted input, reject the archive outright — the total-size check exists to stop resource exhaustion.
Example fix
// before
const entries = safeUnzip(backupBuffer);
// after
const entries = safeUnzip(backupBuffer, {
maxTotalUncompressedSize: 500 * 1024 * 1024,
}); Defensive patterns
Strategy: try-catch
Validate before calling
// Estimate total size from a pre-scan of the central directory; if unavailable, cap the compressed input:
if (buf.length > MAX_ARCHIVE) throw new Error('Refusing to extract oversized archive'); Try / catch
try {
const entries = safeUnzip(buf);
} catch (e) {
if (e instanceof UnsafeZipError && e.zipReason === 'total-size') {
showError(`Archive contents exceed ${e.maxSize} bytes when decompressed`);
} else { throw e; }
} Prevention
- Prune attachments periodically to keep backups under the total-size cap
- Set maxTotalUncompressedSize deliberately based on deployment memory/disk
- Never disable the total-size check for untrusted input; it stops nested zip bombs
When it happens
Trigger: A zip with many individually-small entries whose combined uncompressed size exceeds maxTotalUncompressedSize — many-attachment backup archives, or bomb archives with thousands of small entries crafted to evade per-entry checks.
Common situations: Long-lived budgets accumulating hundreds of attachments until the total backup exceeds the limit; nested/recursive zip bombs (zips containing zips) expanding multiplicatively.
Related errors
- Zip entry "${file.name}" exceeds maximum size of ${maxEntryS
- Zip archive exceeds maximum size of ${maxArchiveSize} bytes
- zip-too-large
- Unsafe zip entry name: ${name}
- Zip archive contains a duplicate entry: ${file.name}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/1d1fdbdc1fce3375.
Report an issue: GitHub.