fix(providers): bound the /models catalog response size in fetch_models - #42930
fix(providers): bound the /models catalog response size in fetch_models#42930youngstar-eth wants to merge 1 commit into
Conversation
|
Positive verification — clean size-bound guard for catalog responses. The dual-check pattern (Content-Length header pre-check + post-read length guard) correctly handles both honest and adversarial servers. Using Observations:
No issues found. |
fetch_models did `json.loads(resp.read().decode())` with no size limit. The endpoint URL comes from the provider profile's base_url/models_url, which is operator/config-controlled (e.g. a self-hosted or community OpenAI-compatible relay). A misconfigured or compromised endpoint could stream a multi-hundred- MB body within the request timeout and force the CLI/gateway to buffer and JSON-parse an unbounded blob (memory spike / OOM). A timeout bounds wall-clock, not size. Check Content-Length and cap the read at _MAX_MODELS_RESPONSE_BYTES (16 MiB), returning None on an oversized response so the caller degrades gracefully to the static fallback model list — mirroring the existing bounded-download pattern in tools/vision_tools.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
03ebfec to
53f8a21
Compare
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: approve
I reviewed this against current GitHub main d1383a6b1450c6c139720b1b01f8b99cc130453f and PR head 53f8a21e8c828819c9032fe706676de6e80b822a.
Validation:
git rev-list --left-right --count upstream/main...refs/remotes/upstream/pr/42930=>125 1;git merge-tree --write-tree upstream/main refs/remotes/upstream/pr/42930wrote tree40455b8434c91c3cb16661b8c277f13170bb5368;git diff --check upstream/main...refs/remotes/upstream/pr/42930passed.- GitHub checks were all successful or skipped at final recheck.
- Current main uses an unbounded
resp.read().decode()inProviderProfile.fetch_models(); this PR changes it to reject oversizedContent-Lengthand read at most_MAX_MODELS_RESPONSE_BYTES + 1bytes before JSON parsing. python -B -m pytest -q tests/providers/test_fetch_models_size_limit.py -p no:cacheproviderpassed: 3 tests.python -B -m py_compile providers/base.py tests/providers/test_fetch_models_size_limit.pypassed.- A direct probe with
_MAX_MODELS_RESPONSE_BYTES = 16confirmedfetch_models()callsread(17)rather than an unbounded read. - CodeRabbit ran successfully and reported only non-blocking test-harness/boundary suggestions; I did not find a blocker in the reviewed security scope.
Signed: GPT-5.5-xhigh in Codex
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused catalog-response hardening. The unbounded read remains on current main at providers/base.py:214, so the underlying issue is real.
Problems
- The patch is based on an older implementation and changes the request open to
urllib.request.urlopen(providers/base.py:214in this PR). Current main intentionally usesopen_credentialed_urlatproviders/base.py:213; that helper enforces credential stripping across cross-origin redirects (hermes_cli/urllib_security.py:112-132). Please retain that helper when adding the bounded read. - The added tests patch
urllib.request.urlopen, but current main no longer invokes it fromfetch_models; use the secure-opener path or the existing local-server style intests/providers/test_fetch_models_base_url.py:11-39. plugins/model-providers/anthropic/__init__.py:33has a separate unbounded catalog read. Please cover it with the same cap or share the bounded-read implementation.
Suggested changes
- Bound
resp.read(max_bytes + 1)afteropen_credentialed_url(...), preserving the existing redirect-security behavior. - Add tests for declared and undeclared oversized bodies through the current production path.
Automated hermes-sweeper review.
|
|
||
| # Bound the response so a misconfigured/hostile catalog endpoint | ||
| # can't force an unbounded in-memory buffer. A timeout limits | ||
| # wall-clock, not size. Oversized responses fall back to the static |
There was a problem hiding this comment.
Current main opens this credential-bearing request through open_credentialed_url, which strips secrets on cross-origin redirects. Please apply the bounded read while retaining that helper rather than switching to urllib.request.urlopen; otherwise this stale patch regresses the redirect-security boundary.
What & why
ProviderProfile.fetch_modelsdidjson.loads(resp.read().decode())with no size limit. The endpoint URL comes from the provider profile'sbase_url/models_url, which is operator/config-controlled (e.g. a self-hosted or community OpenAI-compatible relay). A misconfigured or compromised endpoint could stream a multi-hundred-MB body within the request timeout and force the CLI/gateway to buffer and JSON-parse an unbounded blob (memory spike / OOM); a timeout bounds wall-clock, not size.Check
Content-Lengthand cap the read at_MAX_MODELS_RESPONSE_BYTES(16 MiB), returningNoneon an oversized response so callers degrade gracefully to the static fallback model list — mirroring the existing bounded-download pattern intools/vision_tools.py.Robustness / DoS-hardening fix.
How to test
Platforms
macOS (stdlib
urllib, no platform-specific behaviour).🤖 Generated with Claude Code