From d12b52361148e2c0e281537e467aa0a2277eeb6b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 19:44:40 +0100 Subject: [PATCH] fix(elevenlabs): use guarded TTS fetch --- CHANGELOG.md | 1 + extensions/elevenlabs/tts.ts | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e6e31e10b..050a3a38f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Models/configure: preserve the existing default model when provider auth is re-run from configure while keeping explicit default-setting commands authoritative. Fixes #70696. (#70793) Thanks @Sathvik-1007. - Codex harness/models: keep legacy `codex/*` harness shorthand out of model picker and `/models` choice surfaces while migrating primary legacy refs to canonical `openai/*` plus explicit Codex harness config. (#71193) Thanks @vincentkoc. - Plugins/runtime deps: respect explicit plugin and channel disablement when repairing bundled runtime dependencies, so doctor and health checks no longer install deps for disabled configured channels. - Diagnostics: harden tool and model diagnostic events against hostile errors, blocking listeners, and unsafe stability reason fields. Thanks @vincentkoc. diff --git a/extensions/elevenlabs/tts.ts b/extensions/elevenlabs/tts.ts index d33dcf9e5b1..42f1d12a3b0 100644 --- a/extensions/elevenlabs/tts.ts +++ b/extensions/elevenlabs/tts.ts @@ -5,6 +5,10 @@ import { normalizeSeed, requireInRange, } from "openclaw/plugin-sdk/speech"; +import { + fetchWithSsrFGuard, + ssrfPolicyFromHttpBaseUrlAllowedHostname, +} from "openclaw/plugin-sdk/ssrf-runtime"; import { isValidElevenLabsVoiceId, normalizeElevenLabsBaseUrl } from "./shared.js"; function assertElevenLabsVoiceSettings(settings: { @@ -61,17 +65,15 @@ export async function elevenLabsTTS(params: { const normalizedLanguage = normalizeLanguageCode(languageCode); const normalizedNormalization = normalizeApplyTextNormalization(applyTextNormalization); const normalizedSeed = normalizeSeed(seed); + const normalizedBaseUrl = normalizeElevenLabsBaseUrl(baseUrl); + const url = new URL(`${normalizedBaseUrl}/v1/text-to-speech/${voiceId}`); + if (outputFormat) { + url.searchParams.set("output_format", outputFormat); + } - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - - try { - const url = new URL(`${normalizeElevenLabsBaseUrl(baseUrl)}/v1/text-to-speech/${voiceId}`); - if (outputFormat) { - url.searchParams.set("output_format", outputFormat); - } - - const response = await fetch(url.toString(), { + const { response, release } = await fetchWithSsrFGuard({ + url: url.toString(), + init: { method: "POST", headers: { "xi-api-key": apiKey, @@ -93,13 +95,16 @@ export async function elevenLabsTTS(params: { speed: voiceSettings.speed, }, }), - signal: controller.signal, - }); - + }, + timeoutMs, + policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(normalizedBaseUrl), + auditContext: "elevenlabs.tts", + }); + try { await assertOkOrThrowProviderError(response, "ElevenLabs API error"); return Buffer.from(await response.arrayBuffer()); } finally { - clearTimeout(timeout); + await release(); } }