fix(byteplus): bound video-generation success response reads#96606
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 11:20 PM ET / 03:20 UTC. Summary PR surface: Source -2, Tests +160. Total +158 across 2 files. Reproducibility: yes. Current main clearly calls await response.json() for both BytePlus submit and poll success bodies, and the PR body gives a real TCP node:http proof path showing oversized streams are canceled after the cap. 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. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the plugin-local bounded-read fix after maintainer acceptance of the shared 16 MiB BytePlus success-JSON cap and normal required merge checks. Do we have a high-confidence way to reproduce the issue? Yes. Current main clearly calls await response.json() for both BytePlus submit and poll success bodies, and the PR body gives a real TCP node:http proof path showing oversized streams are canceled after the cap. Is this the best way to solve the issue? Yes. This is the best code shape I found: BytePlus response validation remains local while body reading reuses the SDK-exported bounded JSON helper instead of duplicating provider-local limit logic. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 643410c1f3c0. Label changesLabel justifications:
Evidence reviewedPR surface: Source -2, Tests +160. Total +158 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
|
|
@clawsweeper review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper review |
|
Maintainer local review: merge-ready, reviewed as a batch:
Current CI red check is unrelated to this PR change Autoreview is clean, the 16 MiB cap is acceptable for these provider JSON/status/error/usage bodies, and the changes follow the established shared bounded-reader pattern. This is the best narrow fix for these call sites: no new SDK/API/config surface, no upgrade/backward-compatibility concern beyond the intended oversized-body failure mode, and no expected breaking behavior for normal provider responses. |
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.
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.
What Problem This Solves
The BytePlus video provider read two untrusted success responses with an unbounded
await response.json(), exposing the create + poll loop to OOM / hang.readBytePlusJsonResponseinextensions/byteplus/video-generation-provider.tsparses both the submit task (/contents/generations/tasks) and poll status (/contents/generations/tasks/{id}) success bodies. The BytePlusbaseUrlis user-supplied and the endpoint is untrusted external content, so a hostile or misconfigured host can stream an arbitrarily large (orContent-Length-less, never-ending) JSON body on either path.response.json()buffers the whole payload before parsing, driving the video-generation create + poll loop into memory pressure / OOM or a hang. The sibling video buffer download in the same file was already bounded withreadResponseWithLimit; this closes the two remaining unbounded JSON reads.Changes
readProviderJsonResponse(16 MiB cap =PROVIDER_JSON_RESPONSE_MAX_BYTES, re-exported viaopenclaw/plugin-sdk/provider-http) instead ofawait response.json()— it streams the body, cancels the underlying stream on overflow, andJSON.parses the decoded prefix. No new abstraction: this reuses thereadResponseWithLimit-backed reader already merged in fix(agents): bound provider JSON response reads #95218 / fix(ollama): bound model-discovery JSON response reads #96027 / fix(exa): bound untrusted search JSON response reads #96038.label: "BytePlus video generation failed") and the poll read (label: "BytePlus video status request failed").${label}: malformed JSON response; overflow throws${label}: JSON response exceeds 16777216 bytes. Plugin-scoped — onlyextensions/bytepluschanges.Real behavior proof
Content-Lengthand an oversized/never-ending body must not be buffered whole; theread must stop at the 16 MiB cap, cancel the stream (tear down the socket), and throw a
bounded error — while valid small submit/poll bodies still parse unchanged.
node:httpserver (createServer) bound to127.0.0.1, returning aContent-Length-less body that streams 1 MiB chunks up to a~32 MiB advertised payload (2× the cap). The real exported
readProviderJsonResponse— the exact byte-bounded reader
readBytePlusJsonResponsecalls for both the submitand poll success-JSON paths — was driven against it over a real TCP socket via the
real
fetch, using the two real provider labels verbatim. Node v22.22.0.node --import tsx <paths-loader> proof.mts— start server → drive thereal reader against
/oversizedwith the submit label → repeat with the polllabel → read back the server's observed bytes-on-wire / socket-abort per request → run a
negative control (the OLD unbounded
response.json()-style whole-body read of the sameendpoint) → drive the real reader against small valid
/small-submitand/small-pollbodies.
readProviderJsonResponsethrew... JSON response exceeds 16777216 bytes; the server observed the socket abortedafter ~17–20 MiB (submit
bytesSent=19922944, pollbytesSent=17825792, both ≪ the~32 MiB it would have sent), confirming the stream was cancelled, not drained. The
negative control's old unbounded read pulled the full 33554432 bytes (proving the cap
is load-bearing). The small submit body parsed to
{ id: "task_real_submit" }and thesmall poll body parsed to
status: "succeeded"with thevideo_urlintact.byteplus/arkendpoint (no key; would notreproduce a hostile oversized body); the untrusted-body behavior is fully reproduced with
a local streaming server over the same transport. The proof script is not committed.
Evidence
Real
node:httpterminal proof (real socket, not a vitest in-process fixture):The in-repo Vitest suite for this provider still passes (it keeps
readProviderJsonResponsereal via
importActual, so the byte-bounded reader actually streams and cancels undertest rather than being stubbed — two real-
ReadableStreamoverflow tests for submit andpoll assert the bounded error + that the body is not fully pulled):
oxlint(exit 0) andtsgo -p tsconfig.extensions.json(no errors inextensions/byteplus)are clean on the changed files.
This is the BytePlus video-generation counterpart to the #96027 / #96038 response-limit
campaign, applying the same
readResponseWithLimit-backed bounded reader to the twoprovider-discovery success-JSON reads.
Label: security
AI-assisted.