Skip to content

fix(embedding): bound OpenAI-compatible embedding response reads#96868

Merged
sallyom merged 2 commits into
openclaw:mainfrom
Alix-007:fix/bound-embedding
Jun 28, 2026
Merged

fix(embedding): bound OpenAI-compatible embedding response reads#96868
sallyom merged 2 commits into
openclaw:mainfrom
Alix-007:fix/bound-embedding

Conversation

@Alix-007

Copy link
Copy Markdown
Contributor

What Problem This Solves

Success-path untrusted body reads in the OpenAI-compatible embedding provider used unbounded response.json(), so a user-configured OpenAI-compatible embedding endpoint could stream an oversized JSON body and force OpenClaw to buffer until OOM or hang before parsing.

Changes

  • Replace the OpenAI-compatible embedding success-path response.json() call with shared readProviderJsonResponse, which applies the existing 16 MiB provider JSON cap and cancels the stream on overflow.
  • Add a focused real node:http regression test for an oversized chunked success JSON response that verifies the provider throws the shared cap error and the server sees the socket close before the full body is sent.

Real behavior proof

Behavior addressed: OpenAI-compatible embedding success responses now use the shared bounded JSON reader instead of unbounded response.json().

Real environment tested: local Linux checkout, Node runtime, real loopback node:http server, production createOpenAICompatibleEmbeddingProvider export, production fetch path, no mocked response object for the proof script.

Exact steps:

node scripts/run-vitest.mjs src/plugins/openai-compatible-embedding-provider.test.ts
pnpm tsgo:core
pnpm tsgo:core:test
node --import tsx scratchpad/embedding-bound-proof.mts
.agents/skills/autoreview/scripts/autoreview --mode local

Observed result: focused Vitest passed, both tsgo lanes exited 0, the scratchpad proof produced PASS lines for bounded throw, socket abort, small JSON happy path, and an old unbounded response.json() negative control.

What was not tested: full repository Vitest/CI matrix was not run; this patch only changes the OpenAI-compatible embedding success JSON read path and its focused provider tests.

Evidence

$ node scripts/run-vitest.mjs src/plugins/openai-compatible-embedding-provider.test.ts
[test] starting test/vitest/vitest.unit-fast.config.ts

 RUN  v4.1.8 /media/vdc/0668001470/workSpace/claw-campaign/campaign-wt/embedding

 Test Files  1 passed (1)
      Tests  26 passed (26)
   Start at  07:33:58
   Duration  2.00s (transform 560ms, setup 0ms, import 1.24s, tests 550ms, environment 0ms)

[test] passed 1 Vitest shard in 12.98s

$ pnpm tsgo:core
$ node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo
# exited 0

$ pnpm tsgo:core:test
$ node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo
# exited 0

$ node --import tsx scratchpad/embedding-bound-proof.mts
[proof] node:http loopback server=http://127.0.0.1:41541, cap=16777216 bytes, oversizedBody=67108864 bytes
PASS  bounded throw :: openai-compatible embeddings failed: JSON response exceeds 16777216 bytes
PASS  bounded stream cancelled :: abort=true bytesSent=19988489 (<67108864)
PASS  happy path small JSON parses :: embedding=[0.25,0.5]
PASS  negative control: old unbounded response.json buffers past cap :: bytesSent=67108873 (> 16777216)
PASS  bounded read consumes far less than negative control :: boundedBytesSent=19988489 negativeBytesSent=67108873

$ .agents/skills/autoreview/scripts/autoreview --mode local
autoreview clean: no accepted/actionable findings reported
overall: patch is correct (0.82)

Label: security
AI-assisted.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 4:17 AM ET / 08:17 UTC.

Summary
The branch routes OpenAI-compatible embedding success JSON through the shared bounded provider reader and adds a loopback oversized-stream regression test.

PR surface: Source -3, Tests +120. Total +117 across 2 files.

Reproducibility: yes. Current main clearly uses response.json() on the successful embedding response path, and the PR body provides a loopback node:http proof that the patched path throws at the cap and cancels the socket; I did not rerun it in this read-only review.

Review metrics: 1 noteworthy metric.

  • Successful Response Cap: 1 success path changed from unbounded to 16 MiB. This is security-positive but compatibility-visible for existing users whose OpenAI-compatible embedding endpoint returns very large successful JSON bodies.

Stored data model
Persistent data-model change detected: unknown-data-model-change: src/plugins/openai-compatible-embedding-provider.test.ts, vector/embedding metadata: src/plugins/openai-compatible-embedding-provider.test.ts, vector/embedding metadata: src/plugins/openai-compatible-embedding-provider.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: canonical
Canonical: #96868
Summary: This PR is the best current canonical candidate for bounding OpenAI-compatible embedding success JSON reads because it includes the same source fix as the older PR plus regression coverage and real behavior proof.

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:

  • Get explicit maintainer acceptance that OpenAI-compatible embeddings should use the shared 16 MiB provider JSON cap.

Risk before merge

  • [P1] Successful OpenAI-compatible embedding responses larger than 16 MiB that current main and v2026.6.10 would attempt to parse will now fail with the shared provider cap; maintainers should accept that as intentional hardening before merge.
  • [P1] The older overlapping PR fix(plugins): bound OpenAI-compatible embeddings JSON response reads #96253 still proposes the same source-level fix, so it should be closed or marked superseded if this PR lands.

Maintainer options:

  1. Accept The Shared Provider Cap (recommended)
    Land this PR if maintainers agree OpenAI-compatible embeddings should follow the existing 16 MiB provider JSON guardrail even though larger successful responses will now fail.
  2. Choose An Embedding-Specific Budget
    If valid embedding batches are expected to exceed 16 MiB, revise the patch to use an explicitly approved embedding-specific limit and cover that policy in tests before merge.
  3. Pause For Response-Size Policy
    If the provider response-size policy is unsettled, hold this PR and the older overlapping PR until maintainers choose the intended embedding success-response budget.

Next step before merge

  • No ClawSweeper repair is needed; the remaining action is maintainer acceptance of the compatibility-visible cap plus normal merge gating.

Security
Cleared: The diff narrows an untrusted provider response-read resource-exhaustion path and adds no dependency, workflow, secret, package-resolution, or external code-execution surface.

Review details

Best possible solution:

Land this focused bounded-reader PR after maintainer acceptance of the 16 MiB cap, then close the older overlapping PR as superseded.

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

Yes. Current main clearly uses response.json() on the successful embedding response path, and the PR body provides a loopback node:http proof that the patched path throws at the cap and cancels the socket; I did not rerun it in this read-only review.

Is this the best way to solve the issue?

Yes. Reusing readProviderJsonResponse is the narrowest maintainable fix because it applies the existing provider JSON cap and cancellation helper instead of adding a second custom limiter; an embedding-specific budget is only preferable if maintainers expect valid responses above 16 MiB.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority provider hardening fix with limited blast radius to OpenAI-compatible embedding success responses.
  • merge-risk: 🚨 compatibility: The PR can reject very large successful embedding responses that current main and the latest release would attempt to parse.
  • 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 includes after-fix terminal proof from a real loopback HTTP server through the production provider path, including bounded rejection, stream cancellation, happy path, and negative-control output.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal proof from a real loopback HTTP server through the production provider path, including bounded rejection, stream cancellation, happy path, and negative-control output.
Evidence reviewed

PR surface:

Source -3, Tests +120. Total +117 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 5 -3
Tests 1 120 0 +120
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 122 5 +117

What I checked:

  • Current main success path is unbounded: Current main still calls the local readJsonResponse helper from postEmbeddingRequest, and that helper delegates to response.json() before vector validation. (src/plugins/openai-compatible-embedding-provider.ts:287, 9a735bea03f6)
  • PR source change uses the shared cap: The PR diff imports readProviderJsonResponse and replaces the local response.json() parser with readProviderJsonResponse(response, "openai-compatible embeddings failed"). (src/plugins/openai-compatible-embedding-provider.ts:287, a469fb4f65b1)
  • Shared reader enforces and cancels: readProviderJsonResponse defaults to PROVIDER_JSON_RESPONSE_MAX_BYTES, and readResponseWithLimit cancels the reader when the next chunk exceeds the cap. (src/agents/provider-http-errors.ts:312, 9a735bea03f6)
  • Regression coverage targets the real provider path: The PR adds a node:http oversized success-response server test that drives createOpenAICompatibleEmbeddingProvider, expects the shared cap error, and checks that the server closes before sending the planned body. (src/plugins/openai-compatible-embedding-provider.test.ts:429, a469fb4f65b1)
  • Latest release still has old behavior: v2026.6.10 still uses response.json() for successful OpenAI-compatible embedding responses, so the cap is upgrade-visible for users with very large successful response bodies. (src/plugins/openai-compatible-embedding-provider.ts:287, aa69b12d0086)
  • Contributor proof is real behavior proof: The PR body includes terminal output from a real loopback node:http server through the production provider path, covering bounded throw, socket cancellation, small JSON happy path, and an old unbounded negative control. (a469fb4f65b1)

Likely related people:

  • mbelinky: Merged provider history shows mbelinky authored the OpenAI-compatible embedding provider and later moved it into core. (role: core provider feature owner and refactor contributor; confidence: high; commits: 9f5f3b88457a, 0247fadc4a46, f96ba85c40ff; files: src/plugins/openai-compatible-embedding-provider.ts, src/plugins/openai-compatible-embedding-provider.test.ts)
  • dutifulbob: Merged provider history includes multiple follow-up commits by dutifulbob for compatible provider config paths, aliases, and edge cases around this provider. (role: embedding provider feature contributor; confidence: high; commits: 1533f9a844cf, 6850380cfa89, 76e702af0046; files: src/plugins/openai-compatible-embedding-provider.ts, src/plugins/openai-compatible-embedding-provider.test.ts)
  • Alix-007: Authored the merged Qwen success-response bounded-reader PR using the same shared provider JSON cap pattern on an adjacent untrusted provider response surface. (role: adjacent bounded-response hardening contributor; confidence: medium; commits: 81da8da058b6; files: extensions/qwen/media-understanding-provider.ts, extensions/qwen/media-understanding-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.

@clawsweeper clawsweeper Bot added 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. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 25, 2026
@Alix-007

Copy link
Copy Markdown
Contributor Author

Fixed the actionable check-lint failure from CI: the oversized stream proof test no longer uses a loop condition that oxlint cannot see being updated across the awaited writer helper.

Current head verification:

  • node scripts/run-oxlint-shards.mjs --threads=8
  • node scripts/run-vitest.mjs src/plugins/openai-compatible-embedding-provider.test.ts -- --run (26 tests passed)
  • git diff --check

The remaining compact failure I saw is in src/agents/agent-bundle-mcp-runtime.test.ts, outside this embedding provider surface.

@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.

@sallyom sallyom self-assigned this Jun 28, 2026
@sallyom

sallyom commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Merge-ready for a469fb4.

Local and ClawSweeper review clean, CI green, best narrow fix, follows established pattern, and the 16 MiB cap is acceptable because this OpenAI-compatible embedding path reads inline JSON vectors from /embeddings, not files or media bytes. The response shape is bounded by one data entry per input, OpenClaw's public embeddings bridge already caps request fanout/input size, and typical 3,072-dim float JSON batches stay comfortably below 16 MiB. This is a practical guardrail rather than an upstream hard limit: custom servers with extreme dimensions or very verbose float JSON can be rejected/cancelled instead of letting the runtime buffer an unbounded successful response.

@sallyom
sallyom merged commit f1e4980 into openclaw:main Jun 28, 2026
93 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 29, 2026
…nclaw#96868)

* fix(embedding): bound OpenAI-compatible embedding response reads

* test(embedding): avoid loop-condition lint in stream proof
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…nclaw#96868)

* fix(embedding): bound OpenAI-compatible embedding response reads

* test(embedding): avoid loop-condition lint in stream proof
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…nclaw#96868)

* fix(embedding): bound OpenAI-compatible embedding response reads

* test(embedding): avoid loop-condition lint in stream proof
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 2, 2026
…nclaw#96868)

* fix(embedding): bound OpenAI-compatible embedding response reads

* test(embedding): avoid loop-condition lint in stream proof

(cherry picked from commit f1e4980)
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…nclaw#96868)

* fix(embedding): bound OpenAI-compatible embedding response reads

* test(embedding): avoid loop-condition lint in stream proof
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: 🐚 platinum hermit Good normal PR readiness with ordinary 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