Skip to content

fix(security): zip limits cannot be bypassed by a header that understates sizes (#1561) - #1639

Merged
ZhuchkaTriplesix merged 2 commits into
devfrom
issue/1561-zip-bomb-limits
Oct 17, 2026
Merged

ZhuchkaTriplesix merged 2 commits into
devfrom
issue/1561-zip-bomb-limits

Conversation

@ZhuchkaTriplesix

Copy link
Copy Markdown
Member

Closes #1561.

What was wrong

SafeZipExtractor._validateArchive checks the per-entry, total and ratio limits against entry.size, the uncompressed size the zip header declares. Up to archive 4.4.x that value was only a buffer hint, so a header claiming 1 KB could inflate to gigabytes on file.content.

What changes

archive 4.5.0 closed this in the library: "A zip entry is no longer decoded past the uncompressed size the archive gives for it, so ArchiveFile.size can be checked before reading." Decoding past it throws ArchiveException (LimitedOutputStream / decodeLimited). Since the declared sizes are exactly what _validateArchive limits, actual output per entry and in total is now bounded by the checked limits. That is the bounded streaming inflate the issue asks for, done in the decoder, so there is no second inflater of our own.

  • archive: ^4.5.2 (was ^4.2.0). pubspec.lock is gitignored, so the constraint is the only thing keeping CI and release builds off a vulnerable 4.2–4.4.
  • SafeZipExtractor.readEntry(entry) returns the bytes and turns the decoder's refusal into a SafeZipException ("larger than its header declares"). The doc comment states the dependency on 4.5+.
  • The local installer, HttpMarketplaceRepository.install and the updater's extractZipSecurely read entries through it and report it as MarketplaceException / AppUpdaterException, like the other limit errors.
  • The header-based checks stay as the cheap early reject.

Tests

A zip of 64 KiB of zeros whose local and central headers are rewritten to claim 100 bytes:

  • passes decodeBytes with the tight test limits (that is the bypass);
  • readEntry throws SafeZipException, both from bytes and through readAndDecodeFile.

An honest entry still reads back. On archive 4.4 the lying-zip tests would fail: content returns the full 64 KiB.

Not run locally; CI decides.

…ates sizes (#1561)

SafeZipExtractor checks its limits against the uncompressed sizes the zip
headers declare. Before archive 4.5.0 that size was only a buffer hint and
an entry decoded to whatever its data held, so a crafted header bypassed
every limit. Since 4.5.0 an entry is not decoded past its declared size.

- Require archive ^4.5.2 (pubspec.lock is not committed, so the
  constraint is what CI and release builds resolve against).
- SafeZipExtractor.readEntry reads an entry's bytes and reports the
  refusal as a SafeZipException; the local installer, the HTTP
  marketplace and the updater extract through it.
- Tests: a zip whose headers claim 100 bytes for 64 KiB passes the
  header checks and is refused when read, from bytes and from a file.
@github-actions github-actions Bot added bug Something isn't working stability Theme parser epic label: stability core Core library logic and services P1 High priority / Core capability labels Oct 17, 2026
@ZhuchkaTriplesix
ZhuchkaTriplesix merged commit d79d09f into dev Oct 17, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core Core library logic and services P1 High priority / Core capability stability Theme parser epic label: stability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant