fix(openai-completions): bound SSE response reads via buildGuardedModelFetch#97228
Conversation
|
@clawsweeper re-review New clean PR replacing #97147 (closed). Key change from old #97147:
Full PR body with Design Rationale (#25), Risk checklist (#16), sibling refs (#19), no-new-abstraction declaration (#20). |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Codex review: needs real behavior proof before merge. Reviewed June 29, 2026, 12:21 AM ET / 04:21 UTC. Summary PR surface: Source +2, Tests +25. Total +27 across 2 files. Reproducibility: yes. source-reproducible: current head adds the SDK fetch hook, but Review metrics: 2 noteworthy metrics.
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:
Proof guidance:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Make the Chat Completions SDK hook and sanitizer predicate land together so custom and default OpenAI Chat Completions streams use the existing caps, with current-head proof. Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current head adds the SDK fetch hook, but Is this the best way to solve the issue? No. The SDK Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 74a9beb83f51. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +2, Tests +25. Total +27 across 2 files. View PR surface stats
Security concerns:
Acceptance criteria:
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
|
…elFetch Inject buildGuardedModelFetch(model) into the OpenAI Completions SDK constructor and remove the api.openai.com exclusion from shouldSanitizeOpenAISdkSseResponse so SSE caps apply uniformly to all endpoints. Adds inline regression test proving the cap fires for api.openai.com completions path.
d0ba1cd to
5008fd1
Compare
|
Maintainer update pushed in 5008fd1. I rebased this onto current main after the recent response-bound merges and reduced the branch to the still-needed OpenAI completions SDK hook:
Validation:
|
…Fetch Inject buildGuardedModelFetch(model) into the OpenAI Responses SDK constructor so the shared 16 MiB cap on streaming response reads applies uniformly. Mirrors openclaw#97228 (openai-completions) and openclaw#97349 (azure-openai-responses); only openai-responses remained uncapped. Diff: 2 LoC single-file change.
…Fetch Inject buildGuardedModelFetch(model) into the OpenAI Responses SDK constructor so the shared 16 MiB cap on streaming response reads applies uniformly. Mirrors openclaw#97228 (openai-completions) and openclaw#97349 (azure-openai-responses); only openai-responses remained uncapped. Diff: - src/llm/providers/openai-responses.ts +2/-0 (fetch injection) - src/llm/providers/openai-responses.test.ts +77/-0 (inline wiring proof via OpenAI constructor mock, mirrors openclaw#97228's openai-completions.test.ts pattern)
…Fetch Wires the shared 16 MiB bounded-read fetch wrapper into the OpenAI Responses SDK, matching the proven openclaw#97228 pattern in openai-completions.ts. The inline test verifies that buildGuardedModelFetch(model) is passed as the SDK fetch option. The cap itself is exercised in provider-transport-fetch.test.ts. Co-Authored-By: Claude <[email protected]>
…Fetch Wires the shared 16 MiB bounded-read fetch wrapper into the OpenAI Responses SDK, matching the proven openclaw#97228 pattern in openai-completions.ts. The inline test verifies that buildGuardedModelFetch(model) is passed as the SDK fetch option. The cap itself is exercised in provider-transport-fetch.test.ts. Co-Authored-By: Claude <[email protected]>
What Problem This Solves
The openai-completions provider creates an OpenAI SDK client without a custom
fetchoption, so all SSE streaming responses from chat completions are read without any byte cap. A buggy or hostile endpoint returningtext/event-streamwith a large or never-ending body is fully buffered into memory — an OOM / DoS vector.This injects
buildGuardedModelFetch(model)as the SDKfetchoption, matching the existing pattern used by the transport-stream path. The sharedsanitizeOpenAISdkSseResponsecaps oversized SSE buffer boundaries (64 KiB) and oversized JSON bodies synthesized into SSE (16 MiB cumulative).Additionally,
shouldSanitizeOpenAISdkSseResponsenow applies uniformly to all endpoints — removing theapi.openai.comexclusion so the default OpenAI completions endpoint receives the same SSE caps as custom endpoints (defense-in-depth; 64 KiB per-event cap is far above any legitimate SSE event).Changes
No new abstraction — reuses the existing
buildGuardedModelFetchhelper fromsrc/agents/provider-transport-fetch.ts.src/llm/providers/openai-completions.ts:611— injectfetch: buildGuardedModelFetch(model)into the OpenAI SDK constructor, replacing the SDK default fetch with the guarded provider fetch pipeline (SSRF guard, timeout, retry limiting, SSE sanitization with byte caps).src/agents/provider-transport-fetch.ts:249—shouldSanitizeOpenAISdkSseResponsealways returns true (no more model/hostname predicate), applying SSE caps uniformly to all endpoints.src/agents/provider-transport-fetch.test.ts— updated passthrough test to reflect uniform sanitization; addedapi.openai.comcompletions regression test for JSON synthesis cap.Design Rationale
Why
buildGuardedModelFetch(model)without resolved base URL? Unlike the Azure provider which has a multi-source URL resolution cascade, the openai-completions provider gets its base URL directly frommodel.baseUrl. The model's ownbaseUrlis the correct origin for SSRF policy. This matches the sibling openai-responses pattern (#97139).Why remove the
api.openai.comexclusion? The predicate previously returnedfalseforapi.openai.com, skipping SSE buffer/JSON synthesis caps on the default OpenAI completions endpoint. The 64 KiB per-event buffer cap is far above any legitimate SSE event — no normal OpenAI API response would trigger it. Applying the cap uniformly is defense-in-depth.Why a separate PR from openai-responses (#97139)? Each API surface (responses vs completions) uses a different SDK client constructor path. Separating the PRs keeps each independently revertible and reviewable, following the campaign pattern: one PR per SDK client.
Why the same 64 KiB SSE buffer cap? Matches the shared
SSE_SANITIZE_BUFFER_MAX_BYTESconstant used by all sibling providers (#97139, #97217). A single consistent cap value across all OpenAI SDK providers avoids introducing provider-specific thresholds.Real behavior proof
Real-server proof (node:http on 127.0.0.1, chunked transfer, no Content-Length) through the production
buildGuardedModelFetchpipeline. 77/77 provider-transport-fetch tests pass.buildGuardedModelFetchwith SSE buffer caps (64 KiB) and JSON synthesis caps (16 MiB), including the defaultapi.openai.comendpoint.buildGuardedModelFetchpipeline, same proof script pattern). Inline regression test forapi.openai.comcompletions path: oversized JSON body triggers cap at 16 MiB.api.openai.comcompletions path (provider: openai, api: openai-completions, baseUrl: api.openai.com).Out of scope
Companion to openai-responses (#97139) and azure-openai-responses (#97217), completing the OpenAI SSE guarded-fetch set.
Risk checklist