Skip to content

fix(azure-openai-responses): bound SSE response reads via buildGuardedModelFetch#97349

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
wangmiao0668000666:fix/azure-nonok-lazy-cap-clean
Jun 28, 2026
Merged

fix(azure-openai-responses): bound SSE response reads via buildGuardedModelFetch#97349
vincentkoc merged 1 commit into
openclaw:mainfrom
wangmiao0668000666:fix/azure-nonok-lazy-cap-clean

Conversation

@wangmiao0668000666

Copy link
Copy Markdown
Contributor

What Problem This Solves

The azure-openai-responses provider creates OpenAI/AzureOpenAI SDK clients without a custom fetch option, so all SSE streaming responses are read without any byte cap. A buggy or hostile endpoint returning text/event-stream with a large or never-ending body is fully buffered into memory by the SDK's internal HTTP layer — an OOM / DoS vector.

This injects buildGuardedModelFetch as the SDK fetch option in both the OpenAI-compatible and native AzureOpenAI client constructors, using the resolved Azure base URL (from options, env, resource name, or model.baseUrl) so SSRF policy and exact-origin trust evaluate the actual request origin. The shared sanitizeOpenAISdkSseResponse caps oversized SSE buffer boundaries (64 KiB per unterminated event, from #96989) and oversized JSON bodies synthesized into SSE (16 MiB cumulative).

The non-OK body cap (4xx/5xx) is applied lazily inside the shared sanitizeOpenAISdkSseResponse guard via TransformStream. The previous per-provider eager wrapper in #97217 read the entire non-OK body before the SDK saw the response, breaking the OpenAI SDK's retry/cancel semantics for retryable 4xx/5xx. This PR uses the lazy shared-guard pattern that closed that defect.

Changes

No new abstraction — reuses the existing buildGuardedModelFetch and SSE_SANITIZE_BUFFER_MAX_BYTES from provider-transport-fetch.ts. The new capNonOkResponseBodyLazily helper is a small inline TransformStream that fires the cap on pull, not eager.

  • src/llm/providers/azure-openai-responses.ts:4 — import buildGuardedModelFetch from provider-transport-fetch.js.
  • src/llm/providers/azure-openai-responses.ts:202,206,217 — after resolving baseUrl via resolveAzureConfig, create buildGuardedModelFetch({ ...model, baseUrl }) with the resolved URL. Inject the result as fetch: into both SDK constructor branches (OpenAI path and AzureOpenAI path). No per-provider wrapper.
  • src/agents/provider-transport-fetch.ts:99 — new capNonOkResponseBodyLazily(response, maxBytes) helper: wraps response.body in a pipeThrough(TransformStream) that caps at maxBytes and controller.terminate()s the source once the cap is reached.
  • src/agents/provider-transport-fetch.ts:138sanitizeOpenAISdkSseResponse now applies capNonOkResponseBodyLazily when !response.ok and response.body is present, closing the shared-guard gap for all SDK providers (not just Azure).
  • src/agents/provider-transport-fetch.test.ts — 2 inline tests at the buildGuardedModelFetch describe block:
    • "caps non-OK response body lazily so SDK can still cancel retryable responses" — 100 KiB 429 body → text.length ≤ 64 KiB after await response.text().
    • "preserves SDK ability to cancel retryable non-OK responses before reading body" — 80 KiB 503 body, await response.body?.cancel() does not pull the full payload from the source.

Design Rationale

Why fetch injection with resolved baseUrl? buildGuardedModelFetch uses model.baseUrl for SSRF origin trust and policy resolution. The Azure provider resolves the final base URL from a cascade (option → env → resource name → model.baseUrl). Spreading the resolved URL into the model ensures SSRF and exact-origin trust evaluate the actual request origin.

Why apply the cap in the shared guard instead of per-provider? Closes the gap for every SDK provider in one place with the same SSE_SANITIZE_BUFFER_MAX_BYTES boundary. Per-provider wrappers risk behavioral drift and complicate the fix for sibling providers (OpenAI Responses, OpenAI Completions, etc.).

Why lazy via TransformStream? The OpenAI SDK may want to cancel a retryable 4xx/5xx response and retry before reading the body. An eager wrapper that pre-reads the body in fetch would destroy that capability. A TransformStream only pulls when the consumer pulls, and controller.terminate() is the cap's stop signal — the source is never drained if the consumer cancels.

Why preserve the existing "non-OK small body for error parsing" test path? The lazy cap terminates the source only once accumulated bytes exceed the cap. A small non-OK body (e.g. 4xx JSON error) is unaffected — the consumer reads the full body via response.json() and gets the error message unchanged. The existing "preserves non-OK SSE bodies for provider HTTP error parsing" test continues to pass against the new shared-guard path.

Real behavior proof

Real local HTTP-server proof (local-only proof script, not committed — per checklist #23): a real node:http server on 127.0.0.1 streams a non-OK body through a 3-step vitest proof. The proof script runs the production buildGuardedModelFetch pipeline (with fetchWithSsrFGuard re-pointed at the real fetch() so the network roundtrip is real), and the lazy cap fires inside the production sanitizeOpenAISdkSseResponse.

=== Real local HTTP-server proof: lazy non-OK body cap ===

  PASS  1/3 server bytesSent=73728 → response.text() length=65536 ≤ 64 KiB
            100 KiB 429 body streamed over real HTTP; cap fires at 64 KiB; source not drained

  PASS  2/3 server bytesSent=16384 < 163840 (160 KiB) after cancel
            160 KiB 503 body; SDK cancels body before reading; server only sent 16 KiB (10% of full payload)

  PASS  3/3 small 400 body preserved for error parsing — message="API key expired"
            4xx JSON body of <1 KiB round-trips intact through the lazy cap wrapper

Mock-based regression coverage (committed provider-transport-fetch.test.ts): 78/78 vitest tests pass, including 2 inline tests for the new non-OK cap and SDK cancel behavior, plus the existing "preserves non-OK SSE bodies for provider HTTP error parsing" test which proves small error bodies are not affected by the cap.

Test suite totals (after this patch):

  • provider-transport-fetch.test.ts: 78/78 passed

  • azure-openai-responses.test.ts: 4/4 passed

  • Behavior addressed: Non-OK response bodies (4xx/5xx) are now lazily capped at 64 KiB by the shared sanitizeOpenAISdkSseResponse guard, closing the unbounded response.text() OOM path while preserving the OpenAI SDK's ability to cancel and retry. All Azure Responses requests route through buildGuardedModelFetch with the resolved base URL.

  • Real environment tested: Linux x86_64, Node 22, real node:http server on 127.0.0.1 streaming chunked transfer responses.

  • Exact steps or command run after this patch:

    node scripts/run-vitest.mjs src/agents/provider-transport-fetch.test.ts --run
    node scripts/run-vitest.mjs src/llm/providers/azure-openai-responses.test.ts --run
  • Evidence after fix: 82/82 vitest tests passed across the 2 committed test files. The real-server proof shows the cap truncating a 100 KiB body to exactly 64 KiB (73728 → 65536, byte-for-byte). The cancel test shows the server only sent 16384 bytes (10% of the 160 KiB total) when the SDK cancelled the body before reading, proving the lazy semantics.

  • Observed result after fix: All Azure Responses requests now go through guarded transport (SSRF, timeout, SSE/JSON synthesis caps, plus the new 64 KiB non-OK cap). SDK can still call body.cancel() on a 429/5xx response before reading — verified by the cancel test, which confirms the source stream is not drained on cancel.

  • What was not tested: Live Azure endpoint (proof exercises the same production fetch pipeline against the same attack shapes; real credentials not required). End-to-end test through the Azure SDK constructor requires a real Azure endpoint — the proof exercises the exact sanitizeOpenAISdkSseResponse codepath that buildGuardedModelFetch invokes.

Out of scope

Closes the loop on #97217 (closed as superseded by this PR — see comment thread). Companion to openai-responses (#97139) and openai-completions (#97228) guarded-fetch rollout. The non-OK body cap now lives in the shared guard rather than per-provider.

Diff stats

3 files changed, 119 insertions(+), 1 deletion(-)
  src/agents/provider-transport-fetch.ts        +31/-1   (capNonOkResponseBodyLazily helper + sanitize cap path)
  src/llm/providers/azure-openai-responses.ts  +4/-0    (buildGuardedModelFetch import + fetch injection)
  src/agents/provider-transport-fetch.test.ts  +84/-0   (2 new inline tests: cap + cancel)

Net change: +31 lines production code (the capNonOkResponseBodyLazily helper + fetch injection), +84 lines committed test code, +0 real-server proof in repo. The real local HTTP-server proof was captured locally and lives in the PR body only — not committed, per the proof-script-not-committed checklist rule (#23).

Risk checklist

  • User-visible behavior change? Yes — existing Azure Responses requests now go through guarded transport (SSRF, timeout, SSE/JSON synthesis caps, plus the new 64 KiB non-OK cap). Non-OK error bodies are capped at 64 KiB.
  • Config/env/migration change? No.
  • Security/auth/secrets/network/tool-execution change? Yes — closes OOM/DoS vector via lazy cap on non-OK bodies. No new auth or secrets surface.
  • Highest-risk area: Guarded fetch may fail existing Azure, APIM, private endpoint, or proxy requests that SDK/default fetch previously allowed. Same risk as the transport-stream path's existing guarded fetch rollout. Non-OK body cap could truncate unusually large error responses from custom endpoints at 64 KiB.

…dModelFetch

Inject buildGuardedModelFetch with resolved Azure base URL into both
SDK constructors (OpenAI and AzureOpenAI path) so Azure Responses
requests route through OpenClaw's guarded transport (SSRF policy,
timeout, retry limiting, SSE/JSON synthesis caps).

The non-OK response body cap is applied lazily inside the shared
sanitizeOpenAISdkSseResponse guard via TransformStream — closes the
unbounded response.text() OOM path for hostile 4xx/5xx responses
while preserving the OpenAI SDK's ability to cancel and retry.

- src/llm/providers/azure-openai-responses.ts: pass guardedFetch into
  both OpenAI and AzureOpenAI SDK constructors (no per-provider wrapper)
- src/agents/provider-transport-fetch.ts: add capNonOkResponseBodyLazily
  helper and wire into sanitizeOpenAISdkSseResponse for !response.ok
- src/agents/provider-transport-fetch.test.ts: 2 inline tests
  (cap fires + SDK cancel preserves source)

Co-Authored-By: Claude <[email protected]>
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Re-opening as a clean PR (closes #97217). Final diff:

The #97217 PR had a no-op commit and 4 commits tangled with the eager-wrapper → lazy-cap evolution. This PR is a clean rebase onto latest main with just the 2 essential changes.

@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Jun 28, 2026
@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 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 10:42 PM ET / 02:42 UTC.

Summary
The PR injects buildGuardedModelFetch into both Azure OpenAI Responses SDK client paths and adds a shared lazy cap for non-OK response bodies.

PR surface: Source +34, Tests +84. Total +118 across 3 files.

Reproducibility: yes. Source inspection shows current main returns non-OK bodies unchanged and OpenAI SDK v6.39.1 reads final non-OK bodies with response.text(); the PR body also gives after-fix local HTTP-server output for cap, cancel, and small-error paths.

Review metrics: 3 noteworthy metrics.

  • SDK fetch hooks: 2 added. Both Azure Responses SDK constructor paths switch from SDK default fetch to OpenClaw guarded transport.
  • Shared non-OK cap: 1 added. Every provider path using the shared guarded SDK sanitizer now caps oversized non-OK response bodies.
  • Regression tests: 2 added. The tests cover cap-on-read and cancel-before-read, the two behavior contracts behind the previous eager-read risk.

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:

  • Maintainer explicitly accepts the Azure guarded-transport compatibility risk, or requests a redacted live Azure/APIM/private-endpoint smoke proof.

Risk before merge

  • [P1] Existing Azure Responses deployments using APIM, private endpoints, proxies, or custom endpoints may fail under OpenClaw guarded transport where SDK default fetch previously connected.
  • [P1] Non-OK provider error bodies over 64 KiB will now be truncated, improving DoS resistance but possibly reducing diagnostics for unusually verbose custom endpoint errors.
  • [P1] The PR body includes strong local HTTP-server proof for the attack shape, but not a live Azure/APIM/private-endpoint compatibility smoke.

Maintainer options:

  1. Accept Guarded Azure Transport (recommended)
    Merge as-is after maintainers explicitly accept that Azure Responses now uses OpenClaw guarded transport and caps oversized non-OK error bodies.
  2. Request Live Azure Smoke
    Ask for redacted live Azure, APIM, private-endpoint, or proxy output if compatibility confidence needs to be higher before merge.
  3. Batch SDK Provider Rollout
    Pause this PR until the sibling OpenAI Responses and Completions guarded-fetch PRs are reviewed as one provider-SDK transport rollout.

Next step before merge

  • [P2] The remaining blocker is maintainer acceptance of guarded Azure transport compatibility risk, not an automated code repair.

Security
Cleared: The diff touches the HTTP security boundary but reuses existing guarded-fetch machinery, adds no dependency or secret surface, and shows no concrete supply-chain concern.

Review details

Best possible solution:

Land this guarded-transport shape after maintainers explicitly accept the Azure compatibility and error-body truncation tradeoff, or request redacted live Azure/APIM smoke proof if that acceptance needs more evidence.

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

Yes. Source inspection shows current main returns non-OK bodies unchanged and OpenAI SDK v6.39.1 reads final non-OK bodies with response.text(); the PR body also gives after-fix local HTTP-server output for cap, cancel, and small-error paths.

Is this the best way to solve the issue?

Yes. The shared lazy TransformStream cap fixes the unbounded final error-body path without the earlier eager-read retry/cancel regression, and using the resolved Azure base URL is the right boundary for guarded-fetch policy.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add P2: This is normal-priority provider transport hardening with limited blast radius and no evidence of an active production outage.
  • add merge-risk: 🚨 compatibility: Azure Responses requests move from SDK default fetch to guarded transport, which can reject endpoints that previously connected.
  • add merge-risk: 🚨 availability: The guarded transport and 64 KiB non-OK cap can make private, proxied, malformed, or oversized Azure responses fail or truncate differently than current behavior.

Label justifications:

  • P2: This is normal-priority provider transport hardening with limited blast radius and no evidence of an active production outage.
  • merge-risk: 🚨 compatibility: Azure Responses requests move from SDK default fetch to guarded transport, which can reject endpoints that previously connected.
  • merge-risk: 🚨 availability: The guarded transport and 64 KiB non-OK cap can make private, proxied, malformed, or oversized Azure responses fail or truncate differently than current behavior.
  • 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 output from a real local HTTP server showing the 64 KiB cap, cancel-before-read behavior, and small-error preservation.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output from a real local HTTP server showing the 64 KiB cap, cancel-before-read behavior, and small-error preservation.
Evidence reviewed

PR surface:

Source +34, Tests +84. Total +118 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 35 1 +34
Tests 1 84 0 +84
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 119 1 +118

What I checked:

Likely related people:

  • joshavant: Git blame and GitHub commit metadata show 898ca9741cba5c6ca1c5ba8b41f20b9a2636fd55 introduced the current provider-transport-fetch and azure-openai-responses files in this checkout. (role: introduced current code path; confidence: high; commits: 898ca9741cba; files: src/agents/provider-transport-fetch.ts, src/llm/providers/azure-openai-responses.ts)
  • wangmiao0668000666: They authored the merged shared SSE cap work in fix(provider-transport-fetch): bound SSE buffer to prevent OOM #96989 and the adjacent SDK guarded-fetch rollout PRs, so they have direct recent context beyond this PR. (role: related merged transport hardening author; confidence: medium; commits: fdc638de7c1d, 804f2c60f765, b9bbac662915; files: src/agents/provider-transport-fetch.ts, src/agents/provider-transport-fetch.test.ts)
  • vincentkoc: They merged the shared SSE cap PR and git history shows earlier provider transport seam work in the same runtime area. (role: adjacent provider transport contributor and merger; confidence: medium; commits: b8c7ae1ea8b9, ea4265a82063, 4798e125f40c; files: src/agents/provider-transport-fetch.ts, src/agents/openai-transport-stream.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. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 28, 2026
@vincentkoc
vincentkoc merged commit 66ffad1 into openclaw:main Jun 28, 2026
154 of 163 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 29, 2026
…dModelFetch (openclaw#97349)

Inject buildGuardedModelFetch with resolved Azure base URL into both
SDK constructors (OpenAI and AzureOpenAI path) so Azure Responses
requests route through OpenClaw's guarded transport (SSRF policy,
timeout, retry limiting, SSE/JSON synthesis caps).

The non-OK response body cap is applied lazily inside the shared
sanitizeOpenAISdkSseResponse guard via TransformStream — closes the
unbounded response.text() OOM path for hostile 4xx/5xx responses
while preserving the OpenAI SDK's ability to cancel and retry.

- src/llm/providers/azure-openai-responses.ts: pass guardedFetch into
  both OpenAI and AzureOpenAI SDK constructors (no per-provider wrapper)
- src/agents/provider-transport-fetch.ts: add capNonOkResponseBodyLazily
  helper and wire into sanitizeOpenAISdkSseResponse for !response.ok
- src/agents/provider-transport-fetch.test.ts: 2 inline tests
  (cap fires + SDK cancel preserves source)

Co-authored-by: Claude <[email protected]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 29, 2026
…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.
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 29, 2026
…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)
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…dModelFetch (openclaw#97349)

Inject buildGuardedModelFetch with resolved Azure base URL into both
SDK constructors (OpenAI and AzureOpenAI path) so Azure Responses
requests route through OpenClaw's guarded transport (SSRF policy,
timeout, retry limiting, SSE/JSON synthesis caps).

The non-OK response body cap is applied lazily inside the shared
sanitizeOpenAISdkSseResponse guard via TransformStream — closes the
unbounded response.text() OOM path for hostile 4xx/5xx responses
while preserving the OpenAI SDK's ability to cancel and retry.

- src/llm/providers/azure-openai-responses.ts: pass guardedFetch into
  both OpenAI and AzureOpenAI SDK constructors (no per-provider wrapper)
- src/agents/provider-transport-fetch.ts: add capNonOkResponseBodyLazily
  helper and wire into sanitizeOpenAISdkSseResponse for !response.ok
- src/agents/provider-transport-fetch.test.ts: 2 inline tests
  (cap fires + SDK cancel preserves source)

Co-authored-by: Claude <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…dModelFetch (openclaw#97349)

Inject buildGuardedModelFetch with resolved Azure base URL into both
SDK constructors (OpenAI and AzureOpenAI path) so Azure Responses
requests route through OpenClaw's guarded transport (SSRF policy,
timeout, retry limiting, SSE/JSON synthesis caps).

The non-OK response body cap is applied lazily inside the shared
sanitizeOpenAISdkSseResponse guard via TransformStream — closes the
unbounded response.text() OOM path for hostile 4xx/5xx responses
while preserving the OpenAI SDK's ability to cancel and retry.

- src/llm/providers/azure-openai-responses.ts: pass guardedFetch into
  both OpenAI and AzureOpenAI SDK constructors (no per-provider wrapper)
- src/agents/provider-transport-fetch.ts: add capNonOkResponseBodyLazily
  helper and wire into sanitizeOpenAISdkSseResponse for !response.ok
- src/agents/provider-transport-fetch.test.ts: 2 inline tests
  (cap fires + SDK cancel preserves source)

Co-authored-by: Claude <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…dModelFetch (openclaw#97349)

Inject buildGuardedModelFetch with resolved Azure base URL into both
SDK constructors (OpenAI and AzureOpenAI path) so Azure Responses
requests route through OpenClaw's guarded transport (SSRF policy,
timeout, retry limiting, SSE/JSON synthesis caps).

The non-OK response body cap is applied lazily inside the shared
sanitizeOpenAISdkSseResponse guard via TransformStream — closes the
unbounded response.text() OOM path for hostile 4xx/5xx responses
while preserving the OpenAI SDK's ability to cancel and retry.

- src/llm/providers/azure-openai-responses.ts: pass guardedFetch into
  both OpenAI and AzureOpenAI SDK constructors (no per-provider wrapper)
- src/agents/provider-transport-fetch.ts: add capNonOkResponseBodyLazily
  helper and wire into sanitizeOpenAISdkSseResponse for !response.ok
- src/agents/provider-transport-fetch.test.ts: 2 inline tests
  (cap fires + SDK cancel preserves source)

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

agents Agent runtime and tooling merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. 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