Skip to content

fix(openrouter): bound video response reads#96873

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
Alix-007:fix/bound-openrouter
Jun 28, 2026
Merged

fix(openrouter): bound video response reads#96873
vincentkoc merged 1 commit into
openclaw:mainfrom
Alix-007:fix/bound-openrouter

Conversation

@Alix-007

@Alix-007 Alix-007 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The OpenRouter video provider parsed successful submit and poll JSON with an unbounded response.json() read. Because that response comes from an external endpoint, a buggy or hostile server could stream a large body without Content-Length and force the process to buffer it before parsing, causing an OOM or hang.

Changes

  • Route readOpenRouterVideoJson through the shared readProviderJsonResponse helper, which applies the established 16 MiB provider JSON cap and cancels an overflowing stream.
  • Preserve the existing OpenRouter video generation response malformed mapping for invalid JSON and non-object payloads.
  • Add a focused streamed-response regression that verifies an oversized successful submit body is rejected and canceled.
  • Keep the existing default private-network denial covered in the video provider tests.

Real behavior proof

Behavior addressed

  • OpenRouter video success-path JSON responses stop at the shared 16 MiB cap and abort the streaming response when exceeded.

Real environment tested

  • Local Node.js v22.22.0 process using a node:http loopback TCP server, no Content-Length, real fetch, and the exported OpenRouter video provider builder.

Exact steps

  • node --import tsx scratchpad/openrouter-bound-proof.mjs
  • node scripts/run-vitest.mjs run extensions/openrouter/video-generation-provider.test.ts
  • node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo
  • node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo

Observed result

  • The bounded OpenRouter video success JSON read throws OpenRouter video generation: JSON response exceeds 16777216 bytes, aborts the server socket, and stops before the full oversized body is sent.
  • A raw response.json() negative control buffers the complete oversized body.
  • Small OpenRouter video JSON responses continue to parse normally.
  • The current PR head passes all 18 focused video provider tests.

What was not tested

  • Live OpenRouter API calls.
  • The optional OpenRouter generation-cost lookup is not changed in this PR.

Evidence

$ node --import tsx scratchpad/openrouter-bound-proof.mjs
PASS openrouter video submit success JSON: threw "OpenRouter video generation: JSON response exceeds 16777216 bytes"; socket abort=true; bytesSent=16842764; full=17825792
PASS negative control: raw response.json() buffered 17825792 bytes; bytesSent=17825806; cap=16777216
PASS happy path: OpenRouter video small JSON parsed normally
PASS openrouter node:http bounded success-path proof complete

$ node scripts/run-vitest.mjs run extensions/openrouter/video-generation-provider.test.ts
Test Files  1 passed (1)
Tests  18 passed (18)

Label: security
AI-assisted.

@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 28, 2026, 11:01 AM ET / 15:01 UTC.

Summary
The PR routes OpenRouter video submit/poll success JSON through the shared 16 MiB provider JSON reader and adds focused regression coverage for malformed, oversized, and private-network config behavior.

PR surface: Source +4, Tests +16. Total +20 across 2 files.

Reproducibility: yes. source-reproducible: current main still calls response.json() for OpenRouter video submit/poll success bodies, and the PR body includes loopback terminal proof that the bounded path aborts an oversized no-Content-Length stream.

Review metrics: none identified.

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.

Next step before merge

  • No repair lane is needed; the patch has sufficient proof and no blocking findings, so the remaining action is normal maintainer review and merge gating.

Security
Cleared: The diff narrows an external-response memory/OOM exposure and, after the earlier rollback, does not broaden private-network, credential, dependency, workflow, or supply-chain surface.

Review details

Best possible solution:

Land this narrow bounded-read fix after exact-head CI and maintainer review; keep generation-cost and sibling-provider bounded-read work in their separate PRs.

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

Yes, source-reproducible: current main still calls response.json() for OpenRouter video submit/poll success bodies, and the PR body includes loopback terminal proof that the bounded path aborts an oversized no-Content-Length stream.

Is this the best way to solve the issue?

Yes. Reusing readProviderJsonResponse inside the existing OpenRouter video parser is the narrowest maintainable fix because it covers submit and poll without duplicating byte-limit logic or changing request policy.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a focused OpenRouter provider OOM/hang hardening fix with limited blast radius rather than a core-runtime emergency.
  • 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 terminal proof from a real loopback HTTP stream plus focused test output showing the bounded error, stream abort, negative control, and happy path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal proof from a real loopback HTTP stream plus focused test output showing the bounded error, stream abort, negative control, and happy path.
Evidence reviewed

PR surface:

Source +4, Tests +16. Total +20 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 7 3 +4
Tests 1 21 5 +16
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 28 8 +20

What I checked:

Likely related people:

  • notamicrodose: Authored the original OpenRouter video generation provider and tests, including the current submit/poll parser shape. (role: introduced behavior; confidence: high; commits: 17ef9ef895ad; files: extensions/openrouter/video-generation-provider.ts, extensions/openrouter/video-generation-provider.test.ts)
  • vincentkoc: Recently hardened provider video downloads in the same OpenRouter video provider and its adjacent tests. (role: recent area contributor; confidence: medium; commits: a19225343b45; files: extensions/openrouter/video-generation-provider.ts, extensions/openrouter/video-generation-provider.test.ts)
  • Alix-007: Authored the merged shared bounded provider JSON reader that this PR reuses, so the author has prior merged history on the relevant helper beyond this proposal. (role: adjacent bounded-reader contributor; confidence: high; commits: 2592f8a51a4e; files: src/agents/provider-http-errors.ts, src/agents/provider-http-errors.test.ts)
  • mushuiyu886: Authored the merged OpenRouter video catalog bounded-read PR that uses the same shared-reader pattern in a sibling OpenRouter video surface. (role: adjacent OpenRouter video hardening contributor; confidence: medium; commits: 5715b5500067; files: extensions/openrouter/video-model-catalog.ts, extensions/openrouter/video-generation-provider.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.

@harjothkhara

Copy link
Copy Markdown
Contributor

Non-maintainer review note, focused on one merge-risk surface.

Two-lens pass:

  • Implementer lens: the bounded JSON reader change itself looks well scoped, and the new tests exercise the success-path cap.
  • Steward lens: this PR also changes OpenRouter video request policy. In extensions/openrouter/video-generation-provider.ts, PR head no longer passes allowPrivateNetwork: false when resolving request config, but it does pass the resulting allowPrivateNetwork into submit, polling, and download. extensions/openrouter/video-http.ts then turns that into ssrfPolicy: { allowPrivateNetwork: true } for polling_url / unsigned_urls, including absolute provider-returned URLs. Sibling OpenRouter image/music paths still explicitly deny private-network opt-in, so this is a behavior/security-boundary change outside the bounded-read fix.

I would split or roll back that policy change unless maintainers explicitly want models.providers.openrouter.request.allowPrivateNetwork to authorize OpenRouter video poll/download URLs too. If it stays, please add a focused regression for the intended policy, especially a provider-returned absolute private polling_url or unsigned_urls under public OpenRouter base vs custom/local base.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jun 26, 2026
@Alix-007
Alix-007 force-pushed the fix/bound-openrouter branch from ec24d6d to b5d703f Compare June 26, 2026 08:38
@Alix-007

Copy link
Copy Markdown
Contributor Author

Thanks again for the careful steward-lens catch — you were exactly right. The allowPrivateNetwork: false removal on the video path was unintended scope-creep beyond the bounded-read fix. I've rolled it back (pushed b5d703f): the video submit/poll/download path now resolves with allowPrivateNetwork: false, identical to the image/music siblings, so the SSRF boundary matches main. The PR is now strictly the bounded JSON reader change (16 MiB cap). I also added a symmetric allowPrivateNetwork: false assertion to the video test to guard against this regression (the original test mocked the resolved config without asserting the input, which is why CI stayed green on the drift). video + image regression tests pass. Re-review welcome — thanks for keeping this tight!

@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 rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jun 26, 2026
@Alix-007

Copy link
Copy Markdown
Contributor Author

Merged latest upstream main into this branch to clear the dirty merge state. I kept the newer OpenRouter dynamic inline-image JSON cap from main and preserved the real readProviderJsonResponse path in the test mock.

Verified current head locally:

  • node scripts/run-vitest.mjs extensions/openrouter/image-generation-provider.test.ts -- --run
  • git diff --check

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 26, 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 added status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. labels Jun 26, 2026
@Alix-007
Alix-007 force-pushed the fix/bound-openrouter branch from c058bd3 to 0c9d84c Compare June 27, 2026 00:16
@Alix-007 Alix-007 changed the title fix(openrouter): bound media response reads fix(openrouter): bound video response reads Jun 27, 2026
@clawsweeper clawsweeper Bot removed proof: sufficient ClawSweeper judged the real behavior proof convincing. 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. labels Jun 28, 2026
@Alix-007

Copy link
Copy Markdown
Contributor Author

Current head now includes the reviewer-caught private-network regression fix: the OpenRouter video path again resolves with allowPrivateNetwork: false, and the focused test now asserts that input so the SSRF boundary stays aligned with the image/music siblings.

Verified current head locally:

  • node scripts/run-vitest.mjs extensions/openrouter/video-generation-provider.test.ts -- --run
  • git diff --check

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 28, 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 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. labels Jun 28, 2026
@vincentkoc
vincentkoc merged commit 48f34b1 into openclaw:main Jun 28, 2026
136 of 145 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: openrouter 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: XS 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.

3 participants