mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
fix: use bounded read for bundle download HTTP responses (#3764)
* fix: use bounded read for bundle download HTTP responses 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. * fix: remove duplicate import of MAX_DOWNLOAD_BYTES and read_response_limited
This commit is contained in:
@@ -14,8 +14,8 @@ from pathlib import Path
|
||||
import typer
|
||||
from rich.markup import escape as _escape_markup
|
||||
|
||||
from ..._console import console, err_console
|
||||
from ..._download_security import MAX_DOWNLOAD_BYTES, read_response_limited
|
||||
from ..._console import console, err_console
|
||||
from ...bundler import BundlerError
|
||||
from ...bundler.lib.project import (
|
||||
active_integration,
|
||||
|
||||
@@ -1010,3 +1010,33 @@ def test_bundle_info_resolves_ghes_browser_release_url(project: Path):
|
||||
|
||||
payload = json.loads(result.output)
|
||||
assert payload["id"] == "demo-bundle"
|
||||
|
||||
|
||||
def test_bundle_download_rejects_oversized_response(project: Path, monkeypatch):
|
||||
"""Bundle download rejects responses exceeding MAX_DOWNLOAD_BYTES."""
|
||||
# Monkeypatch to a small limit so the test is fast and low-memory.
|
||||
monkeypatch.setattr(
|
||||
"specify_cli.commands.bundle.MAX_DOWNLOAD_BYTES", 100
|
||||
)
|
||||
|
||||
api_asset_url = "https://api.github.com/repos/org/repo/releases/assets/99"
|
||||
|
||||
def fake_open_url(url, timeout=None, extra_headers=None, redirect_validator=None):
|
||||
# Return a response that exceeds 100 bytes.
|
||||
return FakeBundleResponse(b"x" * 200, url=api_asset_url)
|
||||
|
||||
catalog = project / "catalog.json"
|
||||
write_catalog_file(
|
||||
catalog,
|
||||
{"demo-bundle": catalog_entry_dict("demo-bundle", download_url=api_asset_url)},
|
||||
)
|
||||
_make_catalog_config(catalog, project)
|
||||
|
||||
with patch("specify_cli.authentication.http.open_url", side_effect=fake_open_url):
|
||||
result = runner.invoke(app, ["bundle", "info", "demo-bundle", "--json"])
|
||||
|
||||
# Must fail with a size-limit error, not an unhandled traceback.
|
||||
assert result.exit_code == 1
|
||||
# Rich may wrap the message across lines; normalise whitespace before checking.
|
||||
output_flat = " ".join(result.output.split())
|
||||
assert "exceeds maximum size of 100 bytes" in output_flat
|
||||
|
||||
Reference in New Issue
Block a user