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.
This commit is contained in:
Sasha Mitchell 2026-08-08 15:59:53 +00:00
parent e24a9146e3
commit e04577d8d9
No known key found for this signature in database
2 changed files with 10 additions and 9 deletions

View file

@ -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)")

View file

@ -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