Skip to content

fix(exa): bound untrusted search JSON response reads#96038

Merged
sallyom merged 1 commit into
openclaw:mainfrom
Alix-007:fix/bound-exa-response
Jun 24, 2026
Merged

fix(exa): bound untrusted search JSON response reads#96038
sallyom merged 1 commit into
openclaw:mainfrom
Alix-007:fix/bound-exa-response

Conversation

@Alix-007

Copy link
Copy Markdown
Contributor

What Problem This Solves

The Exa web-search provider parses its success response with an unbounded
await response.json(). response.json() reads the entire body into memory
before parsing, with no byte ceiling and regardless of (or in the absence of) a
Content-Length header. Exa is an external, untrusted source on the
web_search path: a misbehaving, compromised, or hostile endpoint (including a
self-hosted baseUrl override) can stream an arbitrarily large or never-ending
JSON body and force the gateway/plugin to buffer it all, causing memory pressure
or a hang on the search code path.

The error-body read in this same file is already bounded
(readResponseTextLimited, 8 KiB) — the success-JSON read was the remaining
unbounded surface. This change closes it, making this the success-JSON-side
symmetric companion to the #95103 / #95108 response-limit campaign (and to
the already-merged readProviderJsonResponse cap in #95218).

Changes

  • readExaSearchResults now reads the success body through the shared bounded
    reader readResponseWithLimit (re-exported via
    openclaw/plugin-sdk/response-limit-runtime) with a 16 MiB cap, matching the
    limit other bundled providers use, then JSON.parses the decoded prefix.
  • On overflow the helper cancels the underlying stream and throws a bounded
    error (Exa API response exceeds <N> bytes); the existing malformed-JSON
    error message is preserved for backward compatibility.
  • The cap is overridable via an optional { maxBytes } argument so tests can
    exercise the boundary with a small limit.
  • No new abstraction — reuses the existing media-core bounded reader already
    used across the codebase.

Real behavior proof

  • Behavior addressed: An untrusted Exa search success response with no
    Content-Length and an oversized/never-ending body must not be buffered
    whole; the read must stop at the cap, cancel the stream, and throw a bounded
    error — while valid small responses still parse unchanged.
  • Real environment tested: A real node:http server (createServer) bound
    to 127.0.0.1, streaming a JSON body in 64 KiB chunks with no
    Content-Length
    header, driving the real exported readExaSearchResults
    function (imported through the runtime module, plugin-sdk aliases resolved) on
    Node v22.22.0.
  • Exact steps:
    1. Boot the server; GET /huge streams an unbounded JSON object with no
      Content-Length; GET /small returns one valid result.
    2. Drive readExaSearchResults(response, { maxBytes: 1 MiB }) against /huge
      and assert it rejects + the server socket is closed early.
    3. Negative control: run the OLD path await response.json() against the
      same /huge body and measure how many bytes the server pushed.
    4. Drive readExaSearchResults(response) against /small and assert the
      result is parsed intact.
  • Evidence after fix:
    • Bounded case: threw Exa API response exceeds 1048576 bytes; server socket
      closed early (stream cancelled); only 1,114,175 bytes reached the wire —
      near the 1 MiB cap, not the 24 MiB body.
    • Negative control: the unbounded response.json() read pulled the full
      25,166,988 bytes
      (>22x the bounded read), proving the cap is load-bearing.
    • Small case: parsed into exactly one result with url intact, no truncation.
  • Observed result: ALL PROOF ASSERTIONS PASSED. Plus the in-repo Vitest
    suite for this provider passes 13/13 (including a streaming-fixture regression
    test that asserts the oversized read stops before consuming all chunks, a
    well-formed-under-cap test, and the existing malformed-JSON / error-body
    tests). oxlint and tsgo (extensions project) are clean on the changed
    files.
  • What was not tested: I did not hit the live api.exa.ai endpoint (no key
    / would not reproduce a hostile oversized body); the proof uses a local
    server reproducing the exact untrusted-body shape. The 64-bit memory-exhaustion
    end state was not driven to OOM — the proof instead measures bytes-on-wire and
    early socket close, which is the load-bearing signal.

Evidence

[case 1] oversized success JSON, cap=1 MiB, NO content-length
  ok: readExaSearchResults rejected on oversized body
  ok: bounded error message present (got: Exa API response exceeds 1048576 bytes)
  ok: server socket closed early -> stream was cancelled
  ok: bytes on wire stayed near the 1 MiB cap, not the 24 MiB body (sent 1114175)
[negative control] OLD unbounded `await response.json()` buffers whole body
  ok: unbounded read pulled the FULL ~24 MiB body (sent 25166988), proving the cap is load-bearing
  ok: unbounded read consumed >4x the bounded read (25166988 vs 1114175)
[case 3] normal small JSON still parses unchanged
  ok: small body parsed into 1 result
  ok: result content intact (valid small body not truncated)

ALL PROOF ASSERTIONS PASSED
 Test Files  1 passed (1)
      Tests  13 passed (13)

Label: security

AI-assisted.

Exa search success responses were read via an unbounded `await
response.json()`, so a misbehaving or hostile endpoint could stream an
arbitrarily large body into memory before parsing. Read the success
body through the shared bounded reader (16 MiB cap, the same limit other
bundled providers use) and cancel the stream on overflow. This mirrors
the error-body bound already in place and the openclaw#95103/openclaw#95108 response
-limit campaign on the success-JSON side.

AI-assisted.
@Alix-007

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 5:05 PM ET / 21:05 UTC.

Summary
The PR routes Exa search success JSON through the plugin SDK bounded response reader, adds a 16 MiB cap with a test override, and adds streaming regression tests.

PR surface: Source +13, Tests +48. Total +61 across 2 files.

Reproducibility: yes. Current main's Exa success path calls response.json() in readExaSearchResults, and the PR body gives a concrete node:http streaming-server proof plus negative control for the oversized-body behavior.

Review metrics: 1 noteworthy metric.

  • Response-size policy: 1 hard cap added: 16 MiB. This changes the largest successful Exa JSON body accepted at runtime, so maintainers should notice the new failure boundary before merge.

Stored data model
Persistent data-model change detected: serialized state: extensions/exa/src/exa-web-search-provider.runtime.ts, serialized state: extensions/exa/src/exa-web-search-provider.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] Legitimate Exa success responses over 16 MiB will now fail with a bounded error instead of being fully buffered; that is desirable for untrusted-body hardening but still needs maintainer acceptance as a runtime behavior change.

Maintainer options:

  1. Accept the 16 MiB Exa cap (recommended)
    Land as-is if maintainers agree Exa success responses should follow the existing 16 MiB provider body budget and intentionally fail oversized responses.
  2. Tune the cap before merge
    If Exa has legitimate search responses above 16 MiB, adjust the limit or document the provider-family policy before landing.

Next step before merge

  • No automated repair is needed; maintainers should review and accept the 16 MiB fail-closed Exa response policy before merge.

Security
Cleared: The diff adds bounded reading and tests only, changes no dependency/workflow/secret surfaces, and reduces untrusted-response resource-exhaustion exposure.

Review details

Best possible solution:

Land the bounded Exa read with its regression coverage once maintainers accept the 16 MiB response cap; track any broader web-search response-limit sweep separately.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main's Exa success path calls response.json() in readExaSearchResults, and the PR body gives a concrete node:http streaming-server proof plus negative control for the oversized-body behavior.

Is this the best way to solve the issue?

Yes. Reusing the exported plugin SDK bounded reader in the provider-owned Exa parser is the narrowest maintainable fix; a broader sibling sweep can happen separately without blocking this focused hardening.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 654544b6b7c4.

Label changes

Label changes:

  • add P2: This is a normal-priority security/resource-exhaustion hardening for one optional web-search provider path.
  • add merge-risk: 🚨 compatibility: Existing Exa setups that receive successful JSON bodies above 16 MiB would now fail instead of buffering the response.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal proof from a real local HTTP streaming server, a negative control for the old unbounded path, small-response proof, and successful provider tests.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal proof from a real local HTTP streaming server, a negative control for the old unbounded path, small-response proof, and successful provider tests.

Label justifications:

  • P2: This is a normal-priority security/resource-exhaustion hardening for one optional web-search provider path.
  • merge-risk: 🚨 compatibility: Existing Exa setups that receive successful JSON bodies above 16 MiB would now fail instead of buffering the response.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal proof from a real local HTTP streaming server, a negative control for the old unbounded path, small-response proof, and successful provider tests.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal proof from a real local HTTP streaming server, a negative control for the old unbounded path, small-response proof, and successful provider tests.
Evidence reviewed

PR surface:

Source +13, Tests +48. Total +61 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 15 2 +13
Tests 1 48 0 +48
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 63 2 +61

What I checked:

Likely related people:

  • vincentkoc: Local blame on current main points the Exa runtime read path and caller to fa263af, and Vincent also authored nearby merged response-limit campaign work. (role: recent Exa/runtime area contributor; confidence: medium; commits: fa263affd584, b073d7cc11dc, d6cefe26f499; files: extensions/exa/src/exa-web-search-provider.runtime.ts, src/gateway/model-pricing-cache.ts, src/agents/anthropic-transport-stream.ts)
  • Alix-007: Alix authored the merged provider JSON response cap in fix(agents): bound provider JSON response reads #95218, which uses the same bounded-reader pattern this PR applies to Exa. (role: adjacent response-limit contributor; confidence: high; commits: 2592f8a51a4e, a15f8e3aaac5; files: src/agents/provider-http-errors.ts, src/agents/provider-http-errors.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 23, 2026

sallyom commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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 origin/main: clean, no accepted/actionable findings. I also reviewed upgrade/compatibility risk and found no blocking concerns: no config, persisted state, migration, plugin SDK, or provider API compatibility blocker. The only user-visible behavior change is the intended bounded failure path for malformed or oversized JSON.

Best-fix verdict: this is the right narrow fix because it reuses the existing bounded response-body helper instead of adding a broader provider refactor.

@sallyom
sallyom merged commit 605aede into openclaw:main Jun 24, 2026
128 of 135 checks passed
Alix-007 added a commit to Alix-007/openclaw that referenced this pull request Jun 25, 2026
The Copilot usage read in extensions/github-copilot/usage.ts parsed its
HTTP response with an unbounded await res.json(). A hostile or buggy
api.github.com proxy (the proxy endpoint is derived from a user-supplied
token) could stream an unbounded JSON body and drive the usage snapshot
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap,
cancels the stream on overflow, and wraps malformed JSON with the caller
label. Same no-helper-import-to-bounded-reader shape as the openclaw#96027 /
openclaw#96038 response-limit work.

Add a focused regression test: when the usage stream exceeds the JSON
byte cap, fetchCopilotUsage rejects with a bounded-overflow error and the
reader cancels the body mid-flight instead of buffering the full
advertised stream. Existing parse/HTTP-error cases keep passing.
Alix-007 added a commit to Alix-007/openclaw that referenced this pull request Jun 25, 2026
The batch status read (fetchVoyageBatchStatus) parsed its response with an
unbounded await res.json(), and the batch error-file read (readVoyageBatchError)
buffered the whole body via await res.text(). On top of that, the non-OK
(4xx/5xx) diagnostic body was still read unbounded: assertVoyageResponseOk did
await res.text() before throwing, and the non-OK output-file branch in
runVoyageEmbeddingBatches did the same. Voyage base URLs are user-supplied and
reachable via SSRF, so a misbehaving or hostile endpoint could stream an
unbounded body into memory on any of these paths before parsing.

Route the status JSON through the shared readProviderJsonResponse, the error
file through readResponseWithLimit, and now the non-OK diagnostic body through
readResponseWithLimit as well, all under a single 16 MiB cap, cancelling the
stream on overflow before decode/parse. assertVoyageResponseOk preserves its
original "${context}: ${status} ${text}" diagnostic shape for under-cap bodies
and throws a bounded "(error body exceeds <N> bytes)" on overflow; the non-OK
output-file branch now reuses it instead of a duplicate unbounded read. The
existing error-file fail-soft handling (formatUnavailableBatchError) is
preserved, so a capped endpoint degrades gracefully. The submit path already
bounds its body via postJsonWithRetry/maxResponseBytes and is left untouched.

Symmetric counterpart to the openclaw#96027/openclaw#96038 response-limit campaign.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 25, 2026
Exa search success responses were read via an unbounded `await
response.json()`, so a misbehaving or hostile endpoint could stream an
arbitrarily large body into memory before parsing. Read the success
body through the shared bounded reader (16 MiB cap, the same limit other
bundled providers use) and cancel the stream on overflow. This mirrors
the error-body bound already in place and the openclaw#95103/openclaw#95108 response
-limit campaign on the success-JSON side.

AI-assisted.
sallyom pushed a commit that referenced this pull request Jun 25, 2026
#96608)

The batch status read (fetchVoyageBatchStatus) parsed its response with an
unbounded await res.json(), and the batch error-file read (readVoyageBatchError)
buffered the whole body via await res.text(). On top of that, the non-OK
(4xx/5xx) diagnostic body was still read unbounded: assertVoyageResponseOk did
await res.text() before throwing, and the non-OK output-file branch in
runVoyageEmbeddingBatches did the same. Voyage base URLs are user-supplied and
reachable via SSRF, so a misbehaving or hostile endpoint could stream an
unbounded body into memory on any of these paths before parsing.

Route the status JSON through the shared readProviderJsonResponse, the error
file through readResponseWithLimit, and now the non-OK diagnostic body through
readResponseWithLimit as well, all under a single 16 MiB cap, cancelling the
stream on overflow before decode/parse. assertVoyageResponseOk preserves its
original "${context}: ${status} ${text}" diagnostic shape for under-cap bodies
and throws a bounded "(error body exceeds <N> bytes)" on overflow; the non-OK
output-file branch now reuses it instead of a duplicate unbounded read. The
existing error-file fail-soft handling (formatUnavailableBatchError) is
preserved, so a capped endpoint degrades gracefully. The submit path already
bounds its body via postJsonWithRetry/maxResponseBytes and is left untouched.

Symmetric counterpart to the #96027/#96038 response-limit campaign.
sallyom pushed a commit that referenced this pull request Jun 25, 2026
The Copilot usage read in extensions/github-copilot/usage.ts parsed its
HTTP response with an unbounded await res.json(). A hostile or buggy
api.github.com proxy (the proxy endpoint is derived from a user-supplied
token) could stream an unbounded JSON body and drive the usage snapshot
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap,
cancels the stream on overflow, and wraps malformed JSON with the caller
label. Same no-helper-import-to-bounded-reader shape as the #96027 /
#96038 response-limit work.

Add a focused regression test: when the usage stream exceeds the JSON
byte cap, fetchCopilotUsage rejects with a bounded-overflow error and the
reader cancels the body mid-flight instead of buffering the full
advertised stream. Existing parse/HTTP-error cases keep passing.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 26, 2026
openclaw#96608)

The batch status read (fetchVoyageBatchStatus) parsed its response with an
unbounded await res.json(), and the batch error-file read (readVoyageBatchError)
buffered the whole body via await res.text(). On top of that, the non-OK
(4xx/5xx) diagnostic body was still read unbounded: assertVoyageResponseOk did
await res.text() before throwing, and the non-OK output-file branch in
runVoyageEmbeddingBatches did the same. Voyage base URLs are user-supplied and
reachable via SSRF, so a misbehaving or hostile endpoint could stream an
unbounded body into memory on any of these paths before parsing.

Route the status JSON through the shared readProviderJsonResponse, the error
file through readResponseWithLimit, and now the non-OK diagnostic body through
readResponseWithLimit as well, all under a single 16 MiB cap, cancelling the
stream on overflow before decode/parse. assertVoyageResponseOk preserves its
original "${context}: ${status} ${text}" diagnostic shape for under-cap bodies
and throws a bounded "(error body exceeds <N> bytes)" on overflow; the non-OK
output-file branch now reuses it instead of a duplicate unbounded read. The
existing error-file fail-soft handling (formatUnavailableBatchError) is
preserved, so a capped endpoint degrades gracefully. The submit path already
bounds its body via postJsonWithRetry/maxResponseBytes and is left untouched.

Symmetric counterpart to the openclaw#96027/openclaw#96038 response-limit campaign.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 26, 2026
The Copilot usage read in extensions/github-copilot/usage.ts parsed its
HTTP response with an unbounded await res.json(). A hostile or buggy
api.github.com proxy (the proxy endpoint is derived from a user-supplied
token) could stream an unbounded JSON body and drive the usage snapshot
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap,
cancels the stream on overflow, and wraps malformed JSON with the caller
label. Same no-helper-import-to-bounded-reader shape as the openclaw#96027 /
openclaw#96038 response-limit work.

Add a focused regression test: when the usage stream exceeds the JSON
byte cap, fetchCopilotUsage rejects with a bounded-overflow error and the
reader cancels the body mid-flight instead of buffering the full
advertised stream. Existing parse/HTTP-error cases keep passing.
vincentkoc pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 26, 2026
…sonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.
vincentkoc pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 26, 2026
…sonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.
vincentkoc pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…sonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.
vincentkoc added a commit that referenced this pull request Jun 27, 2026
…sonResponse (#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's #96042, #96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
xydigit-zt pushed a commit to xydigit-zt/xydigit-zt-openclaw that referenced this pull request Jun 28, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
Exa search success responses were read via an unbounded `await
response.json()`, so a misbehaving or hostile endpoint could stream an
arbitrarily large body into memory before parsing. Read the success
body through the shared bounded reader (16 MiB cap, the same limit other
bundled providers use) and cancel the stream on overflow. This mirrors
the error-body bound already in place and the openclaw#95103/openclaw#95108 response
-limit campaign on the success-JSON side.

AI-assisted.
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
openclaw#96608)

The batch status read (fetchVoyageBatchStatus) parsed its response with an
unbounded await res.json(), and the batch error-file read (readVoyageBatchError)
buffered the whole body via await res.text(). On top of that, the non-OK
(4xx/5xx) diagnostic body was still read unbounded: assertVoyageResponseOk did
await res.text() before throwing, and the non-OK output-file branch in
runVoyageEmbeddingBatches did the same. Voyage base URLs are user-supplied and
reachable via SSRF, so a misbehaving or hostile endpoint could stream an
unbounded body into memory on any of these paths before parsing.

Route the status JSON through the shared readProviderJsonResponse, the error
file through readResponseWithLimit, and now the non-OK diagnostic body through
readResponseWithLimit as well, all under a single 16 MiB cap, cancelling the
stream on overflow before decode/parse. assertVoyageResponseOk preserves its
original "${context}: ${status} ${text}" diagnostic shape for under-cap bodies
and throws a bounded "(error body exceeds <N> bytes)" on overflow; the non-OK
output-file branch now reuses it instead of a duplicate unbounded read. The
existing error-file fail-soft handling (formatUnavailableBatchError) is
preserved, so a capped endpoint degrades gracefully. The submit path already
bounds its body via postJsonWithRetry/maxResponseBytes and is left untouched.

Symmetric counterpart to the openclaw#96027/openclaw#96038 response-limit campaign.
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
The Copilot usage read in extensions/github-copilot/usage.ts parsed its
HTTP response with an unbounded await res.json(). A hostile or buggy
api.github.com proxy (the proxy endpoint is derived from a user-supplied
token) could stream an unbounded JSON body and drive the usage snapshot
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap,
cancels the stream on overflow, and wraps malformed JSON with the caller
label. Same no-helper-import-to-bounded-reader shape as the openclaw#96027 /
openclaw#96038 response-limit work.

Add a focused regression test: when the usage stream exceeds the JSON
byte cap, fetchCopilotUsage rejects with a bounded-overflow error and the
reader cancels the body mid-flight instead of buffering the full
advertised stream. Existing parse/HTTP-error cases keep passing.
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
hugenshen added a commit to hugenshen/openclaw that referenced this pull request Jul 7, 2026
The fal music generation provider in extensions/fal/music-generation-provider.ts
parsed its HTTP success response with an unbounded await response.json(). A
hostile or buggy fal.ai endpoint — the base URL is user-configurable via
models.providers.fal.baseUrl (resolved by resolveFalHttpRequestConfig in
http-config.ts, defaulting to https://fal.run) — could stream an arbitrarily
large JSON body into memory before parsing, forcing the music generation handler
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap
(PROVIDER_JSON_RESPONSE_MAX_BYTES), cancels the stream on overflow, and wraps
malformed JSON with the caller label. The change is minimal — a single .json()
call replaced by readProviderJsonResponse<unknown> — and preserves the existing
downstream logic unchanged.

Update the test suite: replace fake { json: async () => ... } response stubs
with proper Response objects carrying JSON body streams via a new
jsonBodyResponse() helper, so the real readProviderJsonResponse implementation
actually exercises the byte-bounded reader under test. Preserve the real
readProviderJsonResponse in the provider-http mock (via importOriginal) so the
bounded reader streams and cancels oversized bodies. Add a focused regression
test: when the music generation stream exceeds the JSON byte cap (32 MiB body,
double the 16 MiB cap), generateMusic rejects with a
"fal-music-generation: JSON response exceeds" error and the reader cancels the
body mid-flight (ReadableStream.cancel fires, bytesPulled < 32 MiB). Existing
parse/HTTP-error cases keep passing.

Symmetric counterpart to the openclaw#96027/openclaw#96038/openclaw#96042/openclaw#96606/openclaw#96607 response-limit
campaign.
hugenshen added a commit to hugenshen/openclaw that referenced this pull request Jul 7, 2026
The fal music generation provider in extensions/fal/music-generation-provider.ts
parsed its HTTP success response with an unbounded await response.json(). A
hostile or buggy fal.ai endpoint — the base URL is user-configurable via
models.providers.fal.baseUrl (resolved by resolveFalHttpRequestConfig in
http-config.ts, defaulting to https://fal.run) — could stream an arbitrarily
large JSON body into memory before parsing, forcing the music generation handler
into OOM.

Route the read through the shared readProviderJsonResponse (from
openclaw/plugin-sdk/provider-http), which enforces the 16 MiB byte cap
(PROVIDER_JSON_RESPONSE_MAX_BYTES), cancels the stream on overflow, and wraps
malformed JSON with the caller label. The change is minimal — a single .json()
call replaced by readProviderJsonResponse<unknown> — and preserves the existing
downstream logic unchanged.

Update the test suite: replace fake { json: async () => ... } response stubs
with proper Response objects carrying JSON body streams via a new
jsonBodyResponse() helper, so the real readProviderJsonResponse implementation
actually exercises the byte-bounded reader under test. Preserve the real
readProviderJsonResponse in the provider-http mock (via importOriginal) so the
bounded reader streams and cancels oversized bodies. Add a focused regression
test: when the music generation stream exceeds the JSON byte cap (32 MiB body,
double the 16 MiB cap), generateMusic rejects with a
"fal-music-generation: JSON response exceeds" error and the reader cancels the
body mid-flight (ReadableStream.cancel fires, bytesPulled < 32 MiB). Existing
parse/HTTP-error cases keep passing.

Symmetric counterpart to the openclaw#96027/openclaw#96038/openclaw#96042/openclaw#96606/openclaw#96607 response-limit
campaign.
wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
…sonResponse (openclaw#96772)

* fix(googlechat): replace unbounded response.json() with readProviderJsonResponse

Replace the local readGoogleChatJsonResponse and
readGoogleChatCertsResponse wrappers with the existing SDK helper
readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the
Google Chat API JSON responses are bounded at 16 MiB, matching the
non-streaming cap already used by 15+ other extensions.

What changed:
- extensions/googlechat/src/api.ts: readGoogleChatJsonResponse now
  delegates to readProviderJsonResponse. Removed the local try/catch
  wrapper.
- extensions/googlechat/src/auth.ts: readGoogleChatCertsResponse now
  delegates to readProviderJsonResponse. Error message preserved.
  Removed the local try/catch wrapper.

This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio,
provider JSON reads). No SDK promotion needed — readProviderJsonResponse
is already available in openclaw/plugin-sdk/provider-http.

* fix(googlechat): add inline bounded-read regression tests

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): remove unused variable flagged by oxlint

Co-Authored-By: Claude <[email protected]>

* fix(googlechat): bound api error body reads

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
(cherry picked from commit 4f3d81b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants