Skip to content

Bound mcpb unpack against decompression bombs and malformed central directories#273

Open
aosmcleod wants to merge 1 commit into
modelcontextprotocol:mainfrom
aosmcleod:fix/unpack-hardening
Open

Bound mcpb unpack against decompression bombs and malformed central directories#273
aosmcleod wants to merge 1 commit into
modelcontextprotocol:mainfrom
aosmcleod:fix/unpack-hardening

Conversation

@aosmcleod

Copy link
Copy Markdown

Problem

unpackExtension decompresses the whole archive with unzipSync and no size limit, and trusts the central-directory entry count without bound — a small, highly compressible .mcpb (zip bomb) can exhaust memory and crash the process (#266). Separately, the central-directory pass reads at offsets derived from untrusted ZIP fields with no bounds validation, so a malformed/truncated directory throws a RangeError (item 4 of #267).

Fix

  • Add configurable maxUncompressedBytes (default 1 GiB) and maxEntries (default 100k), enforced from the central directory before decompression.
  • Run the central-directory pass on all platforms with bounds-checked reads (EOCD + each header validated against buffer length), so a malformed directory degrades gracefully instead of throwing. Unix permission extraction is still gated to Unix.

Limitation (called out honestly)

The size cap relies on the archive's declared uncompressed sizes. Fully bounding an archive that deliberately under-declares its sizes requires streaming decompression with a running byte cap (fflate Unzip + ondata), which is a larger rewrite — flagged as a follow-up rather than done here.

Test

Adds test/unpack-limits.test.ts: a normal archive unpacks within defaults; archives exceeding maxUncompressedBytes or maxEntries are rejected without extracting. yarn lint + yarn test (this suite) green.

Fixes #266.

…ories

unpackExtension inflated the whole archive with unzipSync and no size limit,
and trusted the central-directory entry count — a small, highly compressible
.mcpb could exhaust memory. The central-directory parse also read at offsets
derived from untrusted ZIP fields with no bounds checks.

Add configurable maxUncompressedBytes (default 1 GiB) and maxEntries (default
100k) caps, enforced from the central directory before decompression. Make the
central-directory pass run on all platforms with bounds-checked reads so a
truncated/malformed directory can't throw a RangeError (Unix attribute
extraction is still gated to Unix). Adds tests for normal unpack and for
size/entry-limit rejection.

Note: the size cap relies on the archive's declared uncompressed sizes; fully
bounding a deliberately under-declaring archive needs streaming decompression
with a running byte cap, which would be a larger change (follow-up).

Fixes modelcontextprotocol#266 (central-directory bounds is item 4 of modelcontextprotocol#267)
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.

mcpb unpack: unbounded in-memory decompression (zip-bomb DoS)

1 participant