fix(parallel): bound successful web-search JSON response reads#96035
Conversation
The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from openclaw#95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the openclaw#95103/openclaw#95108 response-limit campaign.
Replace the PR-specific 'openclaw#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
|
@clawsweeper review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 5:00 PM ET / 21:00 UTC. Summary PR surface: Source +8, Tests +93. Total +101 across 2 files. Reproducibility: yes. source inspection on current main shows the paid Parallel success path still uses unbounded Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Land the narrow bounded-read change with the added regression tests, keeping the cap in the provider-owned Parallel runtime and the byte-reading mechanics in the shared provider HTTP helper. Do we have a high-confidence way to reproduce the issue? Yes, source inspection on current main shows the paid Parallel success path still uses unbounded Is this the best way to solve the issue? Yes; this is the best small fix because it reuses the shared provider JSON byte-cap helper from the public plugin SDK seam instead of adding a Parallel-local reader or a weaker AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 252673d5b19e. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +8, Tests +93. Total +101 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
|
|
Merge-ready from my review. This follows the same response-body cap pattern as #96042 and the sibling Exa/Parallel/Ollama PRs: replace one unbounded success JSON read from an untrusted provider endpoint with the existing bounded reader, preserve normal small-response behavior, and add focused overflow/cancel coverage. I ran local autoreview in the PR worktree against Best-fix verdict: this is the right narrow fix because it reuses the existing bounded provider JSON helper instead of adding a broader provider refactor. |
…law#96035) * fix(parallel): bound successful web-search JSON response reads The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from openclaw#95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the openclaw#95103/openclaw#95108 response-limit campaign. * docs(parallel): drop upstream PR ref from response-cap comment Replace the PR-specific 'openclaw#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
…law#96035) * fix(parallel): bound successful web-search JSON response reads The Parallel web_search provider parsed its /v1/search success body with an unbounded await res.json(). The body comes from an external web-search upstream, so a hostile or malfunctioning endpoint streaming an unbounded JSON payload could force the runtime to buffer the whole response before parsing, creating memory pressure or a hang on the provider path. Read the success body through the shared readProviderJsonResponse helper with a 16 MiB cap (matching the provider JSON cap from openclaw#95218); on overflow the stream is cancelled and a bounded error is thrown. The error-body path was already bounded (readResponseTextLimited, 8 KiB). Symmetric follow-up to the openclaw#95103/openclaw#95108 response-limit campaign. * docs(parallel): drop upstream PR ref from response-cap comment Replace the PR-specific 'openclaw#95218' annotation with a neutral description of the shared provider JSON cap so the comment stays accurate independent of upstream PR numbering.
What Problem This Solves
The Parallel
web_searchprovider parses its/v1/searchsuccess response with an unboundedawait res.json():That body comes from an external web-search upstream (
api.parallel.ai, or an operator-configuredbaseUrlproxy) and is explicitly treated as untrusted (externalContent.untrusted: true). With noContent-Lengthguard and no byte cap, a hostile or malfunctioning endpoint that streams an unbounded or absurdly large JSON body forces the runtime to buffer the entire payload into memory beforeJSON.parseever runs. On the provider/plugin path that means memory pressure (potential OOM) or an indefinite hang while the runtime keeps draining bytes — a denial-of-service surface driven entirely by the remote side.The sibling error-body path was already hardened (
readResponseTextLimited, 8 KiB cap, added in "fix(parallel): bound search error bodies"). The success path was the remaining unbounded read. This closes it, mirroring the #95103 / #95108 response-limit campaign and reusing the exact same shared helper landed in #95218.Changes
parallel-web-search-provider.runtime.ts: replace unboundedawait res.json()with the sharedreadProviderJsonResponse<ParallelSearchResponse>(res, "Parallel API", { maxBytes: 16 MiB })fromopenclaw/plugin-sdk/provider-http. This reads through the same bounded reader used for binary/provider JSON responses: on overflow it cancels the underlying stream and throws a bounded error; malformed JSON still surfaces a wrapped error. No new abstraction — reuses the existing helper.PARALLEL_SEARCH_RESPONSE_LIMIT_BYTES = 16 * 1024 * 1024constant (matchesreadProviderJsonResponse's default / fix(agents): bound provider JSON response reads #95218'sPROVIDER_JSON_RESPONSE_MAX_BYTES), exported viatestingalongside the existingPARALLEL_ERROR_BODY_LIMIT_BYTES.parallel-web-search-provider.test.ts: add two regression tests — a multi-chunk streamed oversized body that must throw the bounded error, stop pulling chunks early, and cancel the stream; and a well-formed body that still parses under the cap.Real behavior proof
node:httpserver bound to127.0.0.1streaming a JSON body with noContent-Lengthin 1 MiB chunks (64 MiB total, 4× the cap), driving the real exportedexecuteParallelWebSearchProviderTool(...)via a realfetch()Response. Only the SSRF-guarded transport wrapper (which by design refuses loopback) was swapped for a plain fetch; the code under test — the new bounded read — ran unmodified.node --import <tsx+resolve-hook> proof.mts→ (1) drive the real exported function against the oversized stream; (2) negative control reading the same body with an unbounded reader; (3) drive it against a small well-formed body.Parallel API: JSON response exceeds 16777216 bytes.provider: "parallel").Evidence
Regression suite (
node scripts/run-vitest.mjs extensions/parallel/src/parallel-web-search-provider.test.ts --run): 29 passed.oxlinton changed files: clean.tsgo -p tsconfig.extensions.json: no errors inextensions/parallel(only a pre-existing unrelatedcrablinemodule-resolution issue inextensions/qa-lab).Label: security
🤖 AI-assisted: implementation and proof harness authored with AI assistance; all code, tests, and the real-environment proof were reviewed and run locally by the author. Symmetric follow-up to the #95103 / #95108 response-limit campaign.