Skip to content

fix(files): cap per-entry decompressed size during zip extraction#321

Open
DeryFerd wants to merge 1 commit into
myrialabs:mainfrom
DeryFerd:fix/files/per-entry-size-cap-zip
Open

fix(files): cap per-entry decompressed size during zip extraction#321
DeryFerd wants to merge 1 commit into
myrialabs:mainfrom
DeryFerd:fix/files/per-entry-size-cap-zip

Conversation

@DeryFerd

@DeryFerd DeryFerd commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

There was already a total size cap in the zip extraction logic - extractWithinLimit tracks extractedBytes across all entries and stops once the combined output exceeds the configured limit (default 100MB). But it was missing a per-entry check. That means a single file inside the archive could grow without restraint as long as the total stayed under the limit. If someone packs a 99MB entry into a zip alongside a bunch of tiny files, the extractor would happily decompress the whole thing in one shot before the total cap trips.

This becomes a problem when the individual entry is a compressed stream that decompresses to something massive - zip bombs with one oversized entry being the obvious case. The total cap would eventually catch it, but not before the entire entry was fully buffered in memory.

What changed in backend/files/file-archive.ts

Inside the extractWithinLimit function, there is a data callback that receives decompressed chunks per entry. The original code appended every chunk unconditionally, then checked the overall total after. The fix adds a guard right after updating the per-entry byte counter:

fileBytes += chunk.length;
if (fileBytes > limit) {
    failure = new Error(`Entry "${file.name}" exceeds maximum allowed size of ${limitMB}MB`);
    return;
}
chunks.push(chunk);

Key detail: the check happens before chunks.push, so if the entry exceeds the limit, we never store its data in the accumulator array. This avoids wasting memory on a file that is about to be rejected anyway.

The fileBytes variable was already being tracked per-entry - it existed but was only used to allocate the concatenated buffer at the end. I just added the threshold check against it.

Why this matters

Without this, a user could upload a zip containing a single entry that decompresses to 99.9MB and the system would happily keep it all in memory until extraction finishes. With the per-entry cap, it bails as soon as that specific file crosses the limit - which for an aggressively compressed entry could happen just a few kilobytes into the stream.

The behavior is consistent with the total cap: same error message pattern, same limit value, same early-exit strategy.

Validation

  • bun build --no-bundle backend/files/file-archive.ts - compiles fine
  • Logic change is minimal and localized to one callback closure
  • The total cap still runs as before, so both layers are active

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant