fix(providers): bound self-hosted provider discovery JSON reads#95244
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed June 24, 2026, 2:55 PM ET / 18:55 UTC. Summary PR surface: Source +28, Tests +105. Total +133 across 2 files. Reproducibility: yes. Current main and v2026.6.10 still call Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge the bounded-reader patch after current-head proof is refreshed or explicitly accepted for the 16 MiB cap; do not close it as superseded by the Anthropic-only hardening PR. Do we have a high-confidence way to reproduce the issue? Yes. Current main and v2026.6.10 still call Is this the best way to solve the issue? Yes, with a proof/compatibility caveat. Applying the existing bounded reader at the exact AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against b6bc3ed0dbd8. Label changesLabel justifications:
Evidence reviewedPR surface: Source +28, Tests +105. Total +133 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
discoverLlamaCppRuntimeContextTokens and discoverOpenAICompatibleLocalModels parsed their HTTP responses via an unbounded await response.json(). Self-hosted provider base URLs are user-supplied and untrusted (an endpoint reachable via SSRF could stream an unbounded JSON body), so a hostile or buggy endpoint could drive the setup wizard into OOM. Route both reads through the shared byte-bounded reader (readResponseWithLimit from @openclaw/media-core) under a single 4 MiB cap before JSON.parse, mirroring the bound-stream hardening landed for Anthropic error bodies. Overflow cancels the stream and is swallowed by the existing discovery error handling, so a capped endpoint degrades gracefully (returns [] / skips the runtime context probe) instead of buffering the whole body.
b660a25 to
374fa36
Compare
Signed-off-by: sallyom <[email protected]>
|
Merge-ready from my review. I pushed one maintainer tweak in d84f514 to raise This PR is not superseded by #95108. #95108 bounded Anthropic error streams; this PR still targets the separate self-hosted provider discovery reads in Local proof after the cap bump:
Best-fix verdict: still the right narrow fix. The PR applies the existing capped response reader to the exact untrusted discovery reads, preserves the existing fail-soft behavior, and now uses the same 16 MiB budget as the established provider JSON cap pattern. |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
merge-ready, the real-socket proof with 4MiB cap remains valid for the bounded-reader/cancel behavior; the current-head focused tests cover the updated 16 MiB threshold. |
…claw#95244) * fix(providers): bound self-hosted discovery JSON reads discoverLlamaCppRuntimeContextTokens and discoverOpenAICompatibleLocalModels parsed their HTTP responses via an unbounded await response.json(). Self-hosted provider base URLs are user-supplied and untrusted (an endpoint reachable via SSRF could stream an unbounded JSON body), so a hostile or buggy endpoint could drive the setup wizard into OOM. Route both reads through the shared byte-bounded reader (readResponseWithLimit from @openclaw/media-core) under a single 4 MiB cap before JSON.parse, mirroring the bound-stream hardening landed for Anthropic error bodies. Overflow cancels the stream and is swallowed by the existing discovery error handling, so a capped endpoint degrades gracefully (returns [] / skips the runtime context probe) instead of buffering the whole body. * tune self-hosted discovery cap Signed-off-by: sallyom <[email protected]> --------- Signed-off-by: sallyom <[email protected]> Co-authored-by: sallyom <[email protected]>
…claw#95244) * fix(providers): bound self-hosted discovery JSON reads discoverLlamaCppRuntimeContextTokens and discoverOpenAICompatibleLocalModels parsed their HTTP responses via an unbounded await response.json(). Self-hosted provider base URLs are user-supplied and untrusted (an endpoint reachable via SSRF could stream an unbounded JSON body), so a hostile or buggy endpoint could drive the setup wizard into OOM. Route both reads through the shared byte-bounded reader (readResponseWithLimit from @openclaw/media-core) under a single 4 MiB cap before JSON.parse, mirroring the bound-stream hardening landed for Anthropic error bodies. Overflow cancels the stream and is swallowed by the existing discovery error handling, so a capped endpoint degrades gracefully (returns [] / skips the runtime context probe) instead of buffering the whole body. * tune self-hosted discovery cap Signed-off-by: sallyom <[email protected]> --------- Signed-off-by: sallyom <[email protected]> Co-authored-by: sallyom <[email protected]>
Summary
discoverLlamaCppRuntimeContextTokensanddiscoverOpenAICompatibleLocalModelsinsrc/plugins/provider-self-hosted-setup.tsparsed their HTTP responses with an unboundedawait response.json(). Self-hosted provider base URLs are user-supplied and untrusted — an endpoint reachable via SSRF (or simply a buggy/hostile local server) could stream an unbounded JSON body, driving the setup wizard into OOM. Both reads now go through the shared byte-bounded reader under a single cap before parsing.This is the symmetric counterpart to #95108, which hardened the Anthropic Messages error-body read with the same
@openclaw/media-corebound-stream reader. Here we apply the byte-cap to the two provider-discovery JSON reads.Changes
SELF_HOSTED_DISCOVERY_JSON_MAX_BYTEScap (4 MiB) and areadSelfHostedDiscoveryJson(response, label)helper that reads the body viareadResponseWithLimitfrom@openclaw/media-core/read-response-with-limit(cancels the stream on overflow), thenJSON.parses the decoded bytes.discoverLlamaCppRuntimeContextTokens(/props) anddiscoverOpenAICompatibleLocalModels(/models) through the bounded reader instead ofawait response.json(). Overflow throws are swallowed by the existing discovery error handling, so a capped endpoint degrades gracefully:/modelsreturns[],/propsskips the runtime context-token probe — no OOM.Real behavior proof
Behavior addressed: Unbounded
await response.json()on untrusted self-hosted discovery endpoints (/modelsand llama.cpp/props) could buffer an unbounded body into memory (OOM); the reads are now capped at 4 MiB and the stream is cancelled on overflow.Real environment tested: Local clone on Node v22.22.0; a real loopback
httpserver streaming 1 MiB JSON chunks "forever", driven through the realdiscoverOpenAICompatibleLocalModels(realfetchWithSsrFGuard+ undici network stack) over a real socket. Vitest run via the repo'sscripts/run-vitest.mjs.Exact steps or command run after this patch:
node --import tsx scripts/proof-bound-self-hosted-discovery.mts— starts an unbounded-JSON loopback server and calls the real discovery function against it.node scripts/run-vitest.mjs src/plugins/provider-self-hosted-setup.test.ts --runoxlint src/plugins/provider-self-hosted-setup.ts src/plugins/provider-self-hosted-setup.test.tsEvidence after fix:
Vitest:
Test Files 1 passed (1) / Tests 12 passed (12). oxlint: clean.Observed result after fix: The unbounded body is capped at 4 MiB — the loopback server is hung up on (early abort) after ~5 MiB, the overflow error fires, and discovery returns
[]instead of buffering. Negative control: reverting the source toawait response.json()and re-running the two new tests fails withcancelCount expected 1, received 0(the unbounded read never cancels and drained for ~200s instead of being capped), confirming the tests genuinely catch the bug.What was not tested: A live production SSRF chain to a real malicious provider (the threat model is reproduced with a local unbounded-stream server). Full-project
tsccould not run to completion on this constrained host (heap OOM in the type checker itself, unrelated to this change); the changed files type-check cleanly under a scoped check and the vitest transform pipeline.AI-assisted: this change was prepared with AI assistance (analysis, implementation, and proof authoring); all code, tests, and the proof were reviewed and the proof executed locally by the contributor.