Skip to content

fix(image-gen): bound image generation provider JSON response reads#96495

Merged
sallyom merged 2 commits into
openclaw:mainfrom
hugenshen:fix/bound-image-gen-json-responses
Jun 26, 2026
Merged

fix(image-gen): bound image generation provider JSON response reads#96495
sallyom merged 2 commits into
openclaw:mainfrom
hugenshen:fix/bound-image-gen-json-responses

Conversation

@hugenshen

Copy link
Copy Markdown
Contributor

What Problem This Solves

Six image generation providers parse their success responses 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. Image generation providers are external, untrusted
sources: 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 image generation code path.

The affected call sites are:

  • extensions/openrouter/image-generation-provider.ts — OpenRouter image generation result
  • extensions/google/image-generation-provider.ts — Google Imagen image generation result
  • extensions/fal/image-generation-provider.ts — fal image generation result
  • extensions/minimax/image-generation-provider.ts — MiniMax image generation result
  • extensions/openai/image-generation-provider.ts — OpenAI image generation/edit result
  • extensions/vydra/image-generation-provider.ts — Vydra image job submission result

This change closes all six unbounded surfaces, making this the image-generation
companion to the #95103 / #95108 / #95218 response-limit campaign.

Changes

  • Each of the six image-generation providers now reads its success JSON body
    through the shared bounded reader readProviderJsonResponse (from
    openclaw/plugin-sdk/provider-http), which enforces a 16 MiB cap and
    calls the shared readResponseWithLimit helper internally.
  • On overflow the helper cancels the underlying stream and throws a bounded
    error (<label>: JSON response exceeds <N> bytes); existing
    malformed-JSON error messages are preserved for backward compatibility.
  • No new abstraction — reuses the existing media-core bounded reader already
    used across the codebase.
  • Two test files (openai and openrouter) that declare a closed
    vi.mock("openclaw/plugin-sdk/provider-http") factory receive a pass-through
    mock for readProviderJsonResponse so existing response-fixture tests remain
    valid; bounded-reader enforcement is covered by the existing media-core unit
    tests.

Real behavior proof

  • Behavior addressed: An untrusted image generation 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
    readResponseWithLimit helper (the same helper readProviderJsonResponse
    wraps internally) on Node v22.22.0.
  • Exact steps:
    1. Boot the server; GET /huge streams an unbounded JSON object (~24 MiB) with
      no Content-Length; GET /small returns one valid result.
    2. Drive readResponseWithLimit(response, 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 readResponseWithLimit(response, 1 MiB) against /small and assert
      the result is parsed intact.
  • Evidence after fix:
    • Bounded case: threw xai.video-generation: JSON response exceeds 1048576 bytes;
      server socket closed early (stream cancelled); only 1,072,370 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,165,824 bytes (>23x the bounded read), proving the cap is
      load-bearing.
    • Small case: parsed into the expected task result with output URL intact,
      no truncation.
  • Observed result: ALL PROOF ASSERTIONS PASSED. Plus the in-repo Vitest
    suite for the two providers with closed mocks passes — 63/63 for openai
    image-generation and 7/7 for openrouter image-generation — including
    existing generation/edit fixture tests and the Azure OpenAI auth path.
    oxlint is clean on changed files.
  • What was not tested: Live endpoints were not hit (no keys / would not
    reproduce a hostile oversized body). 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 video task status JSON, cap=1024 KiB, NO content-length
  ok: readResponseWithLimit rejected on oversized body — true
  ok: bounded error message present (got: xai.video-generation: JSON response exceeds 1048576 bytes) — true
  ok: bytes on wire stayed near the cap, not the full body (sent 1072370) — true

[negative control] OLD unbounded `await response.json()` buffers whole body
  ok: unbounded read pulled the FULL ~24 MiB body (sent 25165824), proving the cap is load-bearing — true

[case 3] normal small video status JSON still parses unchanged
  ok: small status body parsed into task result — true
  ok: task output url intact (valid small body not truncated) — true

ALL PROOF ASSERTIONS PASSED
 Test Files  1 passed (1)
      Tests  63 passed (63)   ← openai image-generation-provider.test.ts
   Start at  00:17:15
   Duration  4.01s

 Test Files  1 passed (1)
      Tests  7 passed (7)   ← openrouter image-generation-provider.test.ts
   Start at  00:17:25
   Duration  4.08s

Label: security

AI-assisted.

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 6:48 AM ET / 10:48 UTC.

Summary
This PR routes image-generation provider success JSON reads through bounded provider readers, adds an inline-image JSON cap helper exported through plugin-sdk/image-generation, and refreshes provider tests plus SDK surface budgets.

PR surface: Source +151, Tests +251, Generated 0, Other 0. Total +402 across 22 files.

Reproducibility: yes. at source and proof level: current main uses raw response.json() on provider-controlled image-generation success bodies, and the PR body includes loopback proof showing bounded reads stop oversized streams while the old path buffers the full body.

Review metrics: 2 noteworthy metrics.

  • Runtime Success JSON Reads: 8 bounded. This is the semantic hardening surface: image-generation success paths now use image-sized or generic metadata caps instead of raw response.json().
  • Plugin SDK Export Surface: 1 helper export added; 2 budget counters raised. The inline image cap helper becomes plugin-facing through plugin-sdk/image-generation, so maintainers should notice the public SDK surface expansion before merge.

Stored data model
Persistent data-model change detected: serialized state: extensions/deepinfra/image-generation-provider.test.ts, serialized state: extensions/google/image-generation-provider.test.ts, serialized state: extensions/litellm/image-generation-provider.test.ts, serialized state: extensions/openai/index.test.ts, serialized state: extensions/xai/image-generation-provider.test.ts, serialized state: src/image-generation/openai-compatible-image-provider.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: canonical
Canonical: #96495
Summary: This PR is the broad canonical image-generation bounded-read branch; several narrower open or closed PRs overlap parts of the same response-read hardening campaign.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

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

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

Rank-up moves:

  • Maintainers should explicitly accept the image JSON cap policy and coordinate overlapping fal/MiniMax PRs before landing.

Risk before merge

  • [P1] Successful inline/base64 provider responses above the newly derived image JSON byte caps now fail fast; that is intended hardening but compatibility-sensitive for unusually large responses or custom provider/media caps.
  • [P1] Live credentialed vendor endpoints were not exercised; loopback proof covers the hostile oversized-body shape, while fixture tests and CI carry the vendor response-shape confidence.
  • [P1] Open fal and MiniMax sibling PRs overlap or sit adjacent to this hardening work, so maintainers should coordinate landing order to avoid duplicate or conflicting edits.

Maintainer options:

  1. Accept The Image-Sized Cap Policy (recommended)
    Land after required checks if maintainers agree that successful image JSON above the derived media-count budget should fail fast instead of being fully buffered.
  2. Require Representative Live Smoke
    Ask for a small live smoke against representative image providers only if loopback proof plus provider fixtures are not enough confidence for the cap values.
  3. Coordinate Sibling Media PRs
    Keep this PR as the canonical image-generation hardening path and rebase or narrow the fal and MiniMax follow-ups around whichever non-image surfaces remain.

Next step before merge

  • [P2] Human maintainer review should decide the response-cap compatibility tradeoff and sibling PR landing order; there is no narrow automated repair to request.

Security
Cleared: The diff hardens untrusted provider response reads and does not add dependency, lockfile, workflow permission, secret-handling, publishing, or downloaded-code execution surface.

Review details

Best possible solution:

Land this branch or a maintainer-equivalent after accepting the image-sized cap policy, with overlapping fal and MiniMax follow-ups rebased or narrowed around the canonical response-read changes.

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

Yes, at source and proof level: current main uses raw response.json() on provider-controlled image-generation success bodies, and the PR body includes loopback proof showing bounded reads stop oversized streams while the old path buffers the full body.

Is this the best way to solve the issue?

Yes, this is the best implementation layer. Reusing readProviderJsonResponse avoids a custom reader path, and the image-sized cap addresses inline base64 compatibility better than applying the generic 16 MiB JSON cap everywhere.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is normal-priority provider hardening with limited blast radius and no active outage evidence.
  • merge-risk: 🚨 compatibility: The PR can make previously accepted successful image-generation JSON payloads fail when they exceed the newly derived byte caps.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body and maintainer follow-up include terminal-style loopback proof with oversized rejection, early cancellation, negative control, small-response parsing, and targeted provider tests.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and maintainer follow-up include terminal-style loopback proof with oversized rejection, early cancellation, negative control, small-response parsing, and targeted provider tests.
Evidence reviewed

PR surface:

Source +151, Tests +251, Generated 0, Other 0. Total +402 across 22 files.

View PR surface stats
Area Files Added Removed Net
Source 10 170 19 +151
Tests 10 394 143 +251
Docs 0 0 0 0
Config 0 0 0 0
Generated 1 2 2 0
Other 1 2 2 0
Total 22 568 166 +402

What I checked:

Likely related people:

  • steipete: GitHub path history shows Peter Steinberger introduced/shared the OpenAI-compatible image provider, propagated image-generation SSRF policy, and repeatedly maintained image-generation/runtime SDK surfaces. (role: feature introducer and adjacent image-generation owner; confidence: high; commits: 719ec4f2927a, 0a09a8f02fcd, 0135a0a7803f; files: src/image-generation/openai-compatible-image-provider.ts, extensions/openai/image-generation-provider.ts, src/image-generation/image-assets.ts)
  • vincentkoc: Path history shows recent image response schema hardening, shared image transport routing, binary/provider response bounding, and OpenAI Codex image stream cap work near the same response-handling surface. (role: recent image-provider hardening contributor; confidence: high; commits: eb7a082b777d, 0ad2dbd30754, 13be16d69987; files: src/image-generation/image-assets.ts, extensions/openai/image-generation-provider.ts, extensions/minimax/image-generation-provider.ts)
  • RyanLee-Dev: Related merged PR metadata shows RyanLee-Dev introduced the MiniMax image generation provider that this PR now hardens. (role: provider feature introducer; confidence: medium; commits: e2e9f979cafa; files: extensions/minimax/image-generation-provider.ts)
  • Alix-007: Merged PR history shows Alix-007 authored the readProviderJsonResponse implementation reused by this branch. (role: shared bounded-reader contributor; confidence: high; commits: 2592f8a51a4e; files: src/agents/provider-http-errors.ts, src/plugin-sdk/provider-http.ts)
  • joshavant: GitHub path history shows joshavant recently contributed successful provider response read bounding in the shared provider HTTP helper area. (role: recent shared response-reader contributor; confidence: medium; commits: 0a14444924e3; files: src/agents/provider-http-errors.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: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 24, 2026
@sallyom sallyom assigned sallyom and unassigned sallyom Jun 25, 2026
@sallyom
sallyom force-pushed the fix/bound-image-gen-json-responses branch from 69f8599 to cb5b4b2 Compare June 25, 2026 22:05
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: M and removed size: XS labels Jun 25, 2026
@sallyom

sallyom commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Maintainer update: I pushed a signed-off follow-up commit and rebased this branch onto latest origin/main.

The follow-up keeps the bounded JSON reader, but uses an image-sized cap for inline base64 image JSON responses based on the existing image payload cap and provider output count. Fal/Vydra stay on the generic JSON cap because those responses are metadata/job URLs. I also updated the affected OpenAI/Google test fixtures to use real Response bodies and added coverage for valid multi-image inline JSON above 16 MiB plus oversized rejection.

Local proof on the rebased branch:

  • src/image-generation/image-assets.test.ts: 9 passed
  • Google/OpenRouter/MiniMax image provider tests: 31 passed
  • OpenAI image provider tests: 63 passed
  • OpenAI plugin index tests: 13 passed
  • Fal/Vydra image provider tests: 29 passed
  • SDK API baseline check, oxfmt --check, git diff --check: pass
  • Local autoreview: clean

CI should rerun on cb5b4b271dd.

Route success JSON reads through readProviderJsonResponse (16 MiB cap)
in openrouter, google, fal, minimax, openai, and vydra image generation
providers to prevent OOM from oversized or hostile endpoint responses.
Mirrors the response-limit campaign already applied to other provider paths.

AI-assisted.

Co-authored-by: Cursor <[email protected]>
@sallyom
sallyom force-pushed the fix/bound-image-gen-json-responses branch from cb5b4b2 to 0cd677c Compare June 26, 2026 03:38
@clawsweeper clawsweeper Bot removed the rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. label Jun 26, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 26, 2026
@sallyom
sallyom force-pushed the fix/bound-image-gen-json-responses branch from 0cd677c to 9490c8a Compare June 26, 2026 10:33
@openclaw-barnacle openclaw-barnacle Bot added the scripts Repository scripts label Jun 26, 2026
@sallyom

sallyom commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Merge-ready from my review: autoreview is clean, CI is green, and this is the best narrow fix for bounding inline image-generation JSON responses.

The cap sizing is acceptable, and overlapping PRs should rebase to pick up the image-generation limits from this PR rather than duplicating the fix.

@sallyom
sallyom merged commit 527f8f0 into openclaw:main Jun 26, 2026
154 of 156 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
…penclaw#96495)

* fix(image-gen): bound image generation provider JSON response reads

Route success JSON reads through readProviderJsonResponse (16 MiB cap)
in openrouter, google, fal, minimax, openai, and vydra image generation
providers to prevent OOM from oversized or hostile endpoint responses.
Mirrors the response-limit campaign already applied to other provider paths.

AI-assisted.

Co-authored-by: Cursor <[email protected]>

* fix(image-gen): size bounded JSON caps for inline image payloads

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: sallyom <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…penclaw#96495)

* fix(image-gen): bound image generation provider JSON response reads

Route success JSON reads through readProviderJsonResponse (16 MiB cap)
in openrouter, google, fal, minimax, openai, and vydra image generation
providers to prevent OOM from oversized or hostile endpoint responses.
Mirrors the response-limit campaign already applied to other provider paths.

AI-assisted.

Co-authored-by: Cursor <[email protected]>

* fix(image-gen): size bounded JSON caps for inline image payloads

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: sallyom <[email protected]>
LeonidasLux added a commit to LeonidasLux/openclaw that referenced this pull request Jul 7, 2026
… OOM

Replace unbounded `response.json()` in `callUserTokenService` with
`readProviderJsonResponse(response, "msteams.sso-user-token")` —
the same shared bounded reader already deployed across the codebase
(openclaw#95218, openclaw#96322, openclaw#96495, openclaw#96889). Enforces a 16 MiB default cap
and cancels the underlying stream on overflow.

Closes the last unbounded JSON read on the MSTeams SSO token-exchange
path. Error responses were already bounded via `extractProviderErrorDetail`.

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation extensions: deepinfra extensions: fal extensions: google extensions: litellm extensions: minimax extensions: openai extensions: openrouter extensions: xai 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: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: L 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