From 0c0463b2b70f76aa46bdd860efd8ba16c3834711 Mon Sep 17 00:00:00 2001 From: Jim Smith Date: Tue, 14 Apr 2026 22:35:37 -0400 Subject: [PATCH] fix: restore allowPrivateNetwork for self-hosted STT endpoints (#66692) (thanks @jhsmith409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(audio): restore allowPrivateNetwork for self-hosted STT endpoints resolveProviderExecutionContext built the request object passed to transcribeAudio using only sanitizeConfiguredProviderRequest on the tool-level config and entry — which strips allowPrivateNetwork. The provider-level request config (models.providers.*.request) was never included in the merge, so allowPrivateNetwork:true was silently dropped. Additionally, resolveProviderRequestPolicyConfig only read allowPrivate Network from params.allowPrivateNetwork (a direct parameter) and ignored params.request?.allowPrivateNetwork even when it was present. Fix both gaps: - runner.entries.ts: use mergeModelProviderRequestOverrides with sanitizeConfiguredModelProviderRequest(providerConfig?.request) so models.providers.*.request.allowPrivateNetwork flows through to the media execution context - provider-request-config.ts: fall back to params.request?.allowPrivate Network when params.allowPrivateNetwork is undefined Fixes #66691. Regression introduced in v2026.4.14. Co-Authored-By: Claude Sonnet 4.6 * test(media-understanding): assert allowPrivateNetwork flows through resolveProviderExecutionContext Regression test for the bug where providerConfig.request.allowPrivateNetwork was dropped when building the AudioTranscriptionRequest passed to media providers. Verifies that setting allowPrivateNetwork in the provider config reaches the provider's request object after the fix to use mergeModelProviderRequestOverrides + sanitizeConfiguredModelProviderRequest. Co-Authored-By: Claude Sonnet 4.6 * test(media-understanding): tighten allowPrivateNetwork regression types * fix: restore allowPrivateNetwork for self-hosted STT endpoints (#66692) (thanks @jhsmith409) --------- Co-authored-by: Jim Smith Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Ayaan Zaidi --- CHANGELOG.md | 1 + src/agents/provider-request-config.ts | 2 +- src/media-understanding/runner.entries.ts | 5 +- src/media-understanding/runner.proxy.test.ts | 52 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c894b54165..4c84fd340c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai - Telegram/native commands: restore plugin-registry-backed auto defaults for native commands and native skills so Telegram slash commands keep registering when `commands.native` and `commands.nativeSkills` stay on `auto`. (#66843) Thanks @kashevk0. - fix(bluebubbles): replay missed webhook messages after gateway restart via a persistent per-account cursor and `/api/v1/message/query?after=` pass, so messages delivered while the gateway was down no longer disappear. Uses the existing `processMessage` path and is deduped by #66816's inbound GUID cache. (#66857, #66721) Thanks @omarshahine. - Telegram/native commands: keep Telegram command-sync cache process-local so gateway restarts re-register the menu instead of trusting stale on-disk sync state after Telegram cleared commands out-of-band. (#66730) Thanks @nightq. +- Audio/self-hosted STT: restore `models.providers.*.request.allowPrivateNetwork` for audio transcription so private or LAN speech-to-text endpoints stop tripping SSRF blocks after the v2026.4.14 regression. (#66692) Thanks @jhsmith409. ## 2026.4.14 diff --git a/src/agents/provider-request-config.ts b/src/agents/provider-request-config.ts index b2f4bf467bd..624d3fd4909 100644 --- a/src/agents/provider-request-config.ts +++ b/src/agents/provider-request-config.ts @@ -661,7 +661,7 @@ export function resolveProviderRequestPolicyConfig( tls: resolveTlsOverride(params.request?.tls), policy, capabilities, - allowPrivateNetwork: params.allowPrivateNetwork ?? false, + allowPrivateNetwork: params.allowPrivateNetwork ?? params.request?.allowPrivateNetwork ?? false, }; } diff --git a/src/media-understanding/runner.entries.ts b/src/media-understanding/runner.entries.ts index 2a945192ecb..c890872e150 100644 --- a/src/media-understanding/runner.entries.ts +++ b/src/media-understanding/runner.entries.ts @@ -6,7 +6,9 @@ import { } from "../agents/api-key-rotation.js"; import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js"; import { + mergeModelProviderRequestOverrides, mergeProviderRequestOverrides, + sanitizeConfiguredModelProviderRequest, sanitizeConfiguredProviderRequest, } from "../agents/provider-request-config.js"; import type { MsgContext } from "../auto-reply/templating.js"; @@ -429,7 +431,8 @@ async function resolveProviderExecutionContext(params: { ...sanitizeProviderHeaders(params.entry.headers as Record | undefined), }; const headers = Object.keys(mergedHeaders).length > 0 ? mergedHeaders : undefined; - const request = mergeProviderRequestOverrides( + const request = mergeModelProviderRequestOverrides( + sanitizeConfiguredModelProviderRequest(providerConfig?.request), sanitizeConfiguredProviderRequest(params.config?.request), sanitizeConfiguredProviderRequest(params.entry.request), ); diff --git a/src/media-understanding/runner.proxy.test.ts b/src/media-understanding/runner.proxy.test.ts index c919cc85189..febe654951b 100644 --- a/src/media-understanding/runner.proxy.test.ts +++ b/src/media-understanding/runner.proxy.test.ts @@ -178,4 +178,56 @@ describe("runCapability proxy fetch passthrough", () => { }); expect(seenFetchFn).toBeUndefined(); }); + + it("passes allowPrivateNetwork to audio provider when set in providerConfig.request", async () => { + let seenRequest: AudioTranscriptionRequest["request"]; + + await withAudioFixture("openclaw-audio-allowprivatenetwork", async ({ ctx, media, cache }) => { + const providerRegistry = buildProviderRegistry({ + openai: { + id: "openai", + capabilities: ["audio"], + transcribeAudio: async (req: AudioTranscriptionRequest) => { + seenRequest = req.request; + return { text: "ok", model: req.model }; + }, + }, + }); + + const cfg = { + models: { + providers: { + openai: { + apiKey: "test-key", // pragma: allowlist secret + request: { + allowPrivateNetwork: true, + }, + models: [], + }, + }, + }, + tools: { + media: { + audio: { + enabled: true, + models: [{ provider: "openai", model: "whisper-1" }], + }, + }, + }, + } as unknown as OpenClawConfig; + + const result = await runCapability({ + capability: "audio", + cfg, + ctx, + attachments: cache, + media, + providerRegistry, + }); + + expect(result.outputs[0]?.text).toBe("ok"); + }); + + expect(seenRequest?.allowPrivateNetwork).toBe(true); + }); });