Skip to content

Limit zip file entries to 100MB by default - #406

Open
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-358-20261007-1926
Open

claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-358-20261007-1926

Conversation

@claude

@claude claude Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Zip entries read through the Paratext and DBL corpora are now refused when they are larger than 100MB or compressed more than 100:1. Ports machine PR #481.

Changes

  • open_bounded_stream in machine/utils/zip_entry_utils.py is the counterpart of OpenBoundedStream. It raises BadZipFile when the declared size or compression ratio exceeds the limits.
  • BoundedStream in machine/utils/bounded_stream.py raises OSError if more than the limit is actually read, which covers entries whose headers understate their size.
  • ZipEntryStreamContainer, ZipParatextProjectFileHandler and DblBundleTextCorpus now open entries through it. Before, they used archive.read or archive.open with no limit.
  • The C# Write, SetLength and seek handling is not ported. The Python stream is read-only, since only reads are used.

Tests

tests/utils/test_zip_entry_utils.py ports the four C# tests and adds an empty-entry case.

./local_check.sh --agent-strict: 868 passed, 3 skipped.

Closes #358

🤖 Generated with Claude Code


This change is Reviewable

Ports sillsdev/machine#481.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Eli C. Lowry <83078660+Enkidu93@users.noreply.github.com>
for actual_entry_name in self._archive.namelist():
if actual_entry_name.lower() == file_name.lower():
return BytesIO(self._archive.read(actual_entry_name))
with open_bounded_stream(self._archive, actual_entry_name) as stream:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: F1. No test checks that the corpora enforce the limit. tests/utils/test_zip_entry_utils.py covers only the helper. Nothing checks that ZipParatextProjectFileHandler.open, ZipEntryStreamContainer.open_stream or DblBundleTextCorpus refuse an oversized entry, so going back to archive.read would still pass CI. Searched tests/ for open_bounded_stream|BoundedStream|BadZipFile: only the new test file matches. A test for each call site is needed (AGENTS.md: focused tests with every behavior change).

if entry.file_size > max_uncompressed_size:
raise BadZipFile("Entry uncompressed size exceeds maximum allowed limit.")

if entry.compress_size > 0 and entry.file_size / entry.compress_size > max_compression_ratio:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F2. The 100:1 ratio check has no minimum entry size, so a small, very repetitive entry is refused. Deflate shrinks a few KB of padding or repeated empty XML elements to tens of bytes, which is over 100:1, and loading the Paratext project then raises BadZipFile. The callers cannot pass a looser limit. This matches C# OpenBoundedStream, so it is parity and not a port defect. Unverified: I could not run Python here.

def readinto(self, buffer) -> int: # pyright: ignore[reportIncompatibleMethodOverride]
data = self._inner_stream.read(len(buffer))
self._total_bytes_read += len(data)
if self._total_bytes_read > self._max_size:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F3. In Python this runtime limit probably never fires for a zip entry, so the PR body claim that it "covers entries whose headers understate their size" may not hold. CPython ZipExtFile trims its output to the central-directory file_size (data[:self._left]), and open_bounded_stream has already checked that size against the same limit. An understated header ends in a CRC BadZipFile instead. .NET DeflateStream has no such trim, which is why C# needs the check. Unverified: I could not run Python here.

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown
Author

Review summary

  1. Verdict: approve with fixes.
  2. Most important: F1, there are no corpus-level tests that the limit is enforced (machine/corpora/zip_paratext_project_file_handler.py:31, and the same gap in zip_entry_stream_container.py and dbl_bundle_text_corpus.py).
  3. Counts: Critical 0, Important 1, Low 2.
  4. What I ran: I read the diff against sillsdev/machine#481 (gh pr diff 481 -R sillsdev/machine). The port is faithful: same defaults, same checks, the same messages, and the same split between a header-check error and a read-time error (InvalidDataException/IOException became BadZipFile/OSError). The C# Find change in #481 is a refactor only, so there was nothing to port. I searched tests/ for open_bounded_stream|BoundedStream|BadZipFile and only tests/utils/test_zip_entry_utils.py matches.
  5. Not verified: I could not run ./local_check.sh --agent-strict or any Python because execution needed approval, so I have not confirmed the PR's "868 passed". F2 and F3 are reasoned from deflate and CPython zipfile behavior, not reproduced.

Surface changed: ZipParatextProjectFileHandler, ZipEntryStreamContainer and DblBundleTextCorpus in the published wheel now raise BadZipFile for entries over 100MB or compressed more than 100:1, and they have no way to pass a looser limit. There are new modules machine.utils.zip_entry_utils and machine.utils.bounded_stream, but no __all__ change. This matches the C# behavior. No optional dependency changed.

Findings:

  • F1 (Important): new, open.
  • F2 (Low): new, open, unverified. Same behavior as C#.
  • F3 (Low): new, open, unverified.

Reviewed at 6a53b79

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.

Port 'Limit zip file entries to 100MB by default'

0 participants