From e04577d8d91a0c6e067ace328258601ce61998fd Mon Sep 17 00:00:00 2001 From: Sasha Mitchell Date: Sat, 8 Aug 2026 15:59:53 +0000 Subject: [PATCH] fix(runwayml): fetch provider output URLs via safe_get RunwayML video content and TTS download paths called httpx directly on provider-returned output URLs with no SSRF validation. Route those fetches through safe_get/async_safe_get (same as Black Forest Labs image_edit) so private/link-local targets and unsafe redirects are rejected. --- litellm/llms/runwayml/text_to_speech/transformation.py | 9 +++++---- litellm/llms/runwayml/videos/transformation.py | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/litellm/llms/runwayml/text_to_speech/transformation.py b/litellm/llms/runwayml/text_to_speech/transformation.py index 1da8f0c66f0..6831015c38c 100644 --- a/litellm/llms/runwayml/text_to_speech/transformation.py +++ b/litellm/llms/runwayml/text_to_speech/transformation.py @@ -22,6 +22,7 @@ from litellm.llms.base_llm.text_to_speech.transformation import ( TextToSpeechRequestData, ) from litellm.secret_managers.main import get_secret_str +from litellm.litellm_core_utils.url_utils import async_safe_get, safe_get if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj @@ -496,11 +497,11 @@ class RunwayMLTextToSpeechConfig(BaseTextToSpeechConfig): if not isinstance(audio_url, str): raise ValueError(f"RunwayML TTS audio URL is not a string: {audio_url}") - # Download the audio file + # Download the audio file with SSRF guards (provider output URLs are untrusted). from litellm.llms.custom_httpx.http_handler import _get_httpx_client client: Final = _get_httpx_client() - audio_response: Final = client.get(url=audio_url) + audio_response: Final = safe_get(client, audio_url) audio_response.raise_for_status() verbose_logger.debug("RunwayML TTS audio downloaded successfully") @@ -565,11 +566,11 @@ class RunwayMLTextToSpeechConfig(BaseTextToSpeechConfig): if not isinstance(audio_url, str): raise ValueError(f"RunwayML TTS audio URL is not a string: {audio_url}") - # Download the audio file (async) + # Download the audio file (async) with SSRF guards. from litellm.llms.custom_httpx.http_handler import get_async_httpx_client client: Final = get_async_httpx_client(llm_provider=litellm.LlmProviders.RUNWAYML) - audio_response: Final = await client.get(url=audio_url) + audio_response: Final = await async_safe_get(client, audio_url) audio_response.raise_for_status() verbose_logger.debug("RunwayML TTS audio downloaded successfully (async)") diff --git a/litellm/llms/runwayml/videos/transformation.py b/litellm/llms/runwayml/videos/transformation.py index 2e0ae30a192..af1cd49989c 100644 --- a/litellm/llms/runwayml/videos/transformation.py +++ b/litellm/llms/runwayml/videos/transformation.py @@ -6,7 +6,7 @@ from httpx._types import RequestFiles import litellm from litellm.constants import RUNWAYML_DEFAULT_API_VERSION -from litellm.litellm_core_utils.url_utils import encode_url_path_segment +from litellm.litellm_core_utils.url_utils import async_safe_get, encode_url_path_segment, safe_get from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.llms.base_llm.videos.transformation import BaseVideoConfig from litellm.llms.custom_httpx.http_handler import ( @@ -376,9 +376,9 @@ class RunwayMLVideoConfig(BaseVideoConfig): response_data: Final = raw_response.json() video_url: Final = self._extract_video_url_from_response(response_data) - # Download the video from the CloudFront URL synchronously + # Download the video from the provider URL with SSRF guards (same as BFL image_edit). httpx_client: Final[HTTPHandler] = _get_httpx_client() - video_response: Final = httpx_client.get(video_url) + video_response: Final = safe_get(httpx_client, video_url) video_response.raise_for_status() return video_response.content @@ -405,11 +405,11 @@ class RunwayMLVideoConfig(BaseVideoConfig): response_data: Final = raw_response.json() video_url: Final = self._extract_video_url_from_response(response_data) - # Download the video from the CloudFront URL asynchronously + # Download the video from the provider URL with SSRF guards (same as BFL image_edit). async_httpx_client: Final[AsyncHTTPHandler] = get_async_httpx_client( llm_provider=litellm.LlmProviders.RUNWAYML, ) - video_response: Final = await async_httpx_client.get(video_url) + video_response: Final = await async_safe_get(async_httpx_client, video_url) video_response.raise_for_status() return video_response.content