From 665cc3d5371c275b32c2c400b54238e5d96f6761 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 08:37:34 +0100 Subject: [PATCH] fix: guard openai tts fetch --- extensions/openai/speech-provider.test.ts | 13 ++++ extensions/openai/tts.test.ts | 13 ++++ extensions/openai/tts.ts | 72 +++++++++++++---------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/extensions/openai/speech-provider.test.ts b/extensions/openai/speech-provider.test.ts index 4efa3ac13e1..ab4cead5bc5 100644 --- a/extensions/openai/speech-provider.test.ts +++ b/extensions/openai/speech-provider.test.ts @@ -1,6 +1,19 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { buildOpenAISpeechProvider } from "./speech-provider.js"; +vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({ + fetchWithSsrFGuard: async ({ + url, + init, + }: { + url: string; + init?: RequestInit; + }): Promise<{ response: Response; release: () => Promise }> => ({ + response: await globalThis.fetch(url, init), + release: vi.fn(async () => {}), + }), +})); + function isSpeechRequestBody(value: unknown): value is { response_format?: string } { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } diff --git a/extensions/openai/tts.test.ts b/extensions/openai/tts.test.ts index 0b8a764ef0c..7b590064aa0 100644 --- a/extensions/openai/tts.test.ts +++ b/extensions/openai/tts.test.ts @@ -13,6 +13,19 @@ import { resolveOpenAITtsInstructions, } from "./tts.js"; +vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({ + fetchWithSsrFGuard: async ({ + url, + init, + }: { + url: string; + init?: RequestInit; + }): Promise<{ response: Response; release: () => Promise }> => ({ + response: await globalThis.fetch(url, init), + release: vi.fn(async () => {}), + }), +})); + describe("openai tts", () => { const proxyReset = installDebugProxyTestResetHooks(); diff --git a/extensions/openai/tts.ts b/extensions/openai/tts.ts index 5c8570cd67d..3831e781669 100644 --- a/extensions/openai/tts.ts +++ b/extensions/openai/tts.ts @@ -3,6 +3,7 @@ import { isDebugProxyGlobalFetchPatchInstalled, } from "openclaw/plugin-sdk/proxy-capture"; import { extractProviderErrorDetail, trimToUndefined } from "openclaw/plugin-sdk/speech"; +import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime"; export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1"; @@ -106,40 +107,49 @@ export async function openaiTTS(params: { ...(speed != null && { speed }), ...(effectiveInstructions != null && { instructions: effectiveInstructions }), }); - const response = await fetch(`${baseUrl}/audio/speech`, { - method: "POST", - headers: requestHeaders, - body: requestBody, - signal: controller.signal, - }); - if (!isDebugProxyGlobalFetchPatchInstalled()) { - captureHttpExchange({ - url: `${baseUrl}/audio/speech`, + const requestUrl = `${baseUrl}/audio/speech`; + const { response, release } = await fetchWithSsrFGuard({ + url: requestUrl, + init: { method: "POST", - requestHeaders, - requestBody, - response, - transport: "http", - meta: { - provider: "openai", - capability: "tts", - }, - }); - } + headers: requestHeaders, + body: requestBody, + signal: controller.signal, + }, + auditContext: "openai-tts", + }); + try { + if (!isDebugProxyGlobalFetchPatchInstalled()) { + captureHttpExchange({ + url: requestUrl, + method: "POST", + requestHeaders, + requestBody, + response, + transport: "http", + meta: { + provider: "openai", + capability: "tts", + }, + }); + } - if (!response.ok) { - const detail = await extractOpenAiErrorDetail(response); - const requestId = - trimToUndefined(response.headers.get("x-request-id")) ?? - trimToUndefined(response.headers.get("request-id")); - throw new Error( - `OpenAI TTS API error (${response.status})` + - (detail ? `: ${detail}` : "") + - (requestId ? ` [request_id=${requestId}]` : ""), - ); - } + if (!response.ok) { + const detail = await extractOpenAiErrorDetail(response); + const requestId = + trimToUndefined(response.headers.get("x-request-id")) ?? + trimToUndefined(response.headers.get("request-id")); + throw new Error( + `OpenAI TTS API error (${response.status})` + + (detail ? `: ${detail}` : "") + + (requestId ? ` [request_id=${requestId}]` : ""), + ); + } - return Buffer.from(await response.arrayBuffer()); + return Buffer.from(await response.arrayBuffer()); + } finally { + await release(); + } } finally { clearTimeout(timeout); }