fix(image-generation): replace unbounded response.json() with readProviderJsonResponse#96776
Conversation
…viderJsonResponse Replace the unbounded response.json() call in openai-compatible-image-provider with the existing SDK helper readProviderJsonResponse (from openclaw/plugin-sdk/provider-http) so the OpenAI-compatible image generation JSON responses are bounded at 16 MiB, matching the cap already used by 15+ other providers. What changed: - src/image-generation/openai-compatible-image-provider.ts: pass the parsed JSON from readProviderJsonResponse to parseOpenAiCompatibleImageResponse instead of calling response.json() directly. - openai-compatible-image-provider.test.ts: add readProviderJsonResponse to the openclaw/plugin-sdk/provider-http mock so existing tests pass with the new import. This PR applies the same pattern as Alix-007's openclaw#96042, openclaw#96038 (lmstudio, provider JSON reads).
|
Codex review: found issues before merge. Reviewed June 25, 2026, 9:15 PM ET / 01:15 UTC. Summary PR surface: Source +6, Tests +5. Total +11 across 2 files. Reproducibility: yes. at source level: current main and v2026.6.10 use raw Review metrics: 1 noteworthy metric.
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:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Land the shared bounded-read path after maintainers accept a shared image JSON response budget, or adjust this PR to the agreed cap and keep overlapping bounded-read work aligned. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main and v2026.6.10 use raw Is this the best way to solve the issue? No, not yet as a merge-ready fix. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a0b397748fa3. Label changesLabel justifications:
Evidence reviewedPR surface: Source +6, Tests +5. Total +11 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
|
P1: Image responses carry base64-encoded image data that can exceed the default 16 MiB provider JSON cap (4 images × ~7 MiB base64 each = ~28 MiB). Pass a 64 MiB cap to readProviderJsonResponse. P3: Use mode-dependent label for bounded-read errors, matching the pattern already used by assertOkOrThrowHttpError in the same function. Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review Addressed both findings: [P1] Image-sized JSON cap — Changed from default 16 MiB to 64 MiB (mode-dependent label + cap): const parsedBody = await readProviderJsonResponse(
response,
mode === "edit"
? `${options.label} image edit`
: `${options.label} image generation`,
{ maxBytes: 64 * 1024 * 1024 },
);64 MiB covers worst-case base64 image payloads (4 images × ~7 MiB base64 ≈ ~28 MiB + headroom). [P3] Mode-dependent label — Now uses "image edit" for edit mode, "image generation" for generate mode, matching the pattern already used by Pre-flight: oxlint ✅, tsgo ✅, vitest 5/5 ✅ |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review Upgraded to 🦞-grade proof with all 6 gaps addressed: [P1] CLI/real-server product-path proof — Upgraded from 3 PASS to 6 PASS with BEFORE/AFTER/negative control:
[P1] Threat model — Now documents specific attack vectors: SSRF-redirected target, misconfigured proxy, CDN cache compromise Pre-flight: vitest 5/5 ✅, oxlint ✅, tsgo ✅ |
|
@clawsweeper re-review Added - Evidence after fix: field inside ## Real behavior proof section (CI check requires this legacy field). All other sections unchanged. |
|
🦞👀 Command router queued. I will update this comment with the next step. |
|
@clawsweeper re-review P1 finding from the previous review: proof now exercises the full production chain (real HTTP server → readProviderJsonResponse → parseOpenAiCompatibleImageResponse), not just the helper in isolation. 7/7 assertions pass:
No code changes — proof-only update. |
|
After studying Alix-007's #96893, I've come to understand that it covers the same bounded-read surface (image-generation OpenAI-compatible provider) more comprehensively — it patches 3 files (microsoft-foundry, openai, and the shared core) while this PR only covers 2. One consideration for @Alix-007: image generation responses (base64-encoded images) can exceed the default 16 MiB Otherwise, your implementation is the better fix. Closing in favor of #96893. |
|
Closed in favor of #96893 (Alix-007) |
What Problem This Solves
src/image-generation/openai-compatible-image-provider.ts:272callsawait response.json()with no byte-level cap. This is an OOM vulnerability: a hostile, compromised, or misconfigured image generation endpoint — or anything sitting between the gateway and the provider (a misconfigured proxy, a CDN cache, an SSRF-redirected target) — can return a success status with noContent-Lengthand stream an arbitrarily large JSON payload.Because
response.json()buffers the entire body before parsing, that unbounded body is fully read into memory before any size check runs. Image generation responses are particularly sensitive because they contain base64-encoded image data: 4 images × ~7 MiB base64 ≈ ~28 MiB, but without a cap a hostile endpoint can send 70 MiB+ and exhaust process memory.This is part of the broader bounded-read campaign across the OpenClaw codebase — sibling PRs apply the same pattern to Google Chat, Dashscope video, OpenAI video, and other providers. The shared
readProviderJsonResponsehelper (16 MiB default) already covers 15+ providers; this PR extends it to image generation with an image-appropriate 64 MiB cap.Changes
src/image-generation/openai-compatible-image-provider.ts:273-279— replaceparseOpenAiCompatibleImageResponse(await response.json(), ...)withreadProviderJsonResponse(response, label, { maxBytes: 64 * 1024 * 1024 })+parseOpenAiCompatibleImageResponse(parsedBody, ...). Uses 64 MiB cap (vs default 16 MiB) because image JSON responses contain base64-encoded data (4 images × ~7 MiB base64 ≈ ~28 MiB). Label is mode-dependent: "image edit" for edit mode, matching the surroundingassertOkOrThrowHttpErrorpattern.src/image-generation/openai-compatible-image-provider.test.ts— addreadProviderJsonResponseto test mock.Real behavior proof
Drives the production chain —
readProviderJsonResponse→parseOpenAiCompatibleImageResponse— over a realnode:httpserver (chunked transfer, no Content-Length, real sockets). Covers the changed provider factory end-to-end. Node v22.22.0.response.json()on image generation HTTP responses; after the fix, the full production chain (readProviderJsonResponse→parseOpenAiCompatibleImageResponse) protects against oversized streams. The happy path correctly returns parsed images; the oversized path throws a bounded error near the 64 MiB cap.node:httpserver (127.0.0.1, chunked transfer, no Content-Length) driving the combined production chain:readProviderJsonResponse(response, label, { maxBytes })thenparseOpenAiCompatibleImageResponse(parsedBody, ...). Node v22.22.0.node --import tsx _proof_image_gen.mts(working tree only, not committed)response.json()(unbounded) reads full under-cap body successfully, same data through capped path also succeeds.Out of scope
response.json()call sites — covered by sibling bounded-read PRs (fix(googlechat): replace unbounded response.json() with readProviderJsonResponse #96772, fix(video-generation): bound dashscope JSON response reads at 16 MiB #96782, etc.).Risk
response.json()toreadProviderJsonResponse+parseOpenAiCompatibleImageResponse. Error behavior preserves the same upstream catch path. Byte cap is 64 MiB — invisible to normal-sized image responses (typically < 1 MiB JSON wrapper). Mode-aware label matches existingassertOkOrThrowHttpErrorpattern.Diff stats
AI-assisted: implemented and proof-tested with AI assistance; reviewed by a human before submission.