Skip to content

fix: use bounded read for bundle download HTTP responses - #3764

Open
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/unbounded-bundle-download
Open

fix: use bounded read for bundle download HTTP responses#3764
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/unbounded-bundle-download

Conversation

@Quratulain-bilal

Copy link
Copy Markdown
Contributor

Summary

The bundle download in commands/bundle/__init__.py used unbounded resp.read() to read HTTP responses into memory. A malicious or misconfigured catalog server could return an arbitrarily large payload causing OOM.

Changes

  • src/specify_cli/commands/bundle/__init__.py: Replaced resp.read() with read_response_limited(resp, max_bytes=MAX_DOWNLOAD_BYTES) capped at 50 MiB, consistent with how other download paths in the codebase enforce bounded reads.

Testing

All 31 tests in test_bundle_cli.py pass after the fix.

Security Impact

This is a Medium severity fix - it closes a potential memory exhaustion vector against the bundle download endpoint. The 50 MiB limit is appropriate for bundle artifacts (ZIPs, archives) which are larger than JSON metadata but still bounded.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Bounds bundle HTTP response reads at 50 MiB to prevent memory exhaustion.

Changes:

  • Replaces unbounded response reading with the shared limited-read helper.
Show a summary per file
File Description
src/specify_cli/commands/bundle/__init__.py Enforces the bundle download size limit.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Medium

) as resp:
_require_https(f"bundle '{entry_id}'", resp.geturl())
raw = resp.read()
raw = read_response_limited(resp, max_bytes=MAX_DOWNLOAD_BYTES)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added test_bundle_download_rejects_oversized_response which monkeypatches MAX_DOWNLOAD_BYTES to 100, serves 200 bytes via a fake response, and asserts the command fails with exceeds maximum size of 100 bytes. This proves the size-limit bound is retained in the bundle CLI download path.

@Quratulain-bilal
Quratulain-bilal force-pushed the fix/unbounded-bundle-download branch from e5c651b to b8c293d Compare July 27, 2026 20:37
@Quratulain-bilal

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Regression test added in the latest push:

est_bundle_download_rejects_oversized_response monkeypatches MAX_DOWNLOAD_BYTES to 100 bytes, serves a 200-byte response via ake_open_url, and verifies the command exits with a size-limit error (exit code 1, no unhandled traceback).

All 32 bundle CLI tests pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread tests/contract/test_bundle_cli.py Outdated

# Must fail with a size-limit error, not an unhandled traceback.
assert result.exit_code == 1
assert "Error:" in result.output or "exceeds" in result.output.lower()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed: the assertion now checks for exceeds maximum size of 100 bytes specifically, which only appears when read_response_limited enforces the bound. A plain YAML/schema parse failure would not produce this message.

Comment on lines 14 to 16
from ..._download_security import MAX_DOWNLOAD_BYTES, read_response_limited

import typer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed: moved the from ..._download_security import ... after import typer to match the third-party-before-first-party convention established in commands/init.py:11-15.

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please address Copilot feedback

The bundle download used unbounded resp.read() to read HTTP responses
into memory. A malicious or misconfigured catalog server could return
an arbitrarily large payload causing OOM.

Replace with read_response_limited() capped at MAX_DOWNLOAD_BYTES
(50 MiB), consistent with how other download paths in the codebase
enforce bounded reads.

Add regression test that monkeypatches MAX_DOWNLOAD_BYTES to 100 bytes
and verifies oversized responses are rejected.
@Quratulain-bilal
Quratulain-bilal force-pushed the fix/unbounded-bundle-download branch from b8c293d to d222d92 Compare July 27, 2026 21:53
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.

3 participants