From fc580ae1ecd8d0b7a0d8bb3e96061f7a3e0a8d1c Mon Sep 17 00:00:00 2001 From: user <70670632+stuxf@users.noreply.github.com> Date: Fri, 1 May 2026 00:32:02 +0000 Subject: [PATCH] fix(videos): encode the variant query param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``variant`` is user-controlled (passed through from ``litellm.video_content(variant=...)``) and was interpolated raw into the URL query string. A value like ``thumbnail&extra=1`` would inject additional query parameters into the upstream request — the same class of issue this PR's path-segment encoding addresses. Wrap the value in ``quote(value, safe="")`` so ``&`` / ``=`` / ``#`` cannot terminate the ``variant`` value or open a new parameter. Adds a regression test asserting that a malicious ``thumbnail&extra=1`` ends up percent-encoded in the URL, and that the legitimate ``thumbnail`` value still round-trips cleanly. --- litellm/llms/openai/videos/transformation.py | 7 ++++- .../test_openai_video_transformation.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/litellm/llms/openai/videos/transformation.py b/litellm/llms/openai/videos/transformation.py index 6f25e4addb..2d165a7d7d 100644 --- a/litellm/llms/openai/videos/transformation.py +++ b/litellm/llms/openai/videos/transformation.py @@ -1,6 +1,7 @@ import mimetypes from io import BufferedReader, BytesIO from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast +from urllib.parse import quote import httpx from httpx._types import RequestFiles @@ -228,7 +229,11 @@ class OpenAIVideoConfig(BaseVideoConfig): # Construct the URL for video content download url = f"{api_base.rstrip('/')}/{encoded_video_id}/content" if variant is not None: - url = f"{url}?variant={variant}" + # Encode the user-controlled ``variant`` so a value like + # ``thumbnail&extra=1`` cannot inject additional query params + # into the upstream request — same hardening rationale as the + # path-segment encoding above. + url = f"{url}?variant={quote(variant, safe='')}" # No additional data needed for GET content request data: Dict[str, Any] = {} diff --git a/tests/test_litellm/llms/openai/videos/test_openai_video_transformation.py b/tests/test_litellm/llms/openai/videos/test_openai_video_transformation.py index 0c7edfbb8d..c15554a46a 100644 --- a/tests/test_litellm/llms/openai/videos/test_openai_video_transformation.py +++ b/tests/test_litellm/llms/openai/videos/test_openai_video_transformation.py @@ -20,6 +20,34 @@ def test_video_content_request_encodes_video_id_path_segment(): assert params == {} +def test_video_content_request_encodes_variant_query_param(): + """``variant`` is user-controlled and was previously interpolated raw + into the query string. A value like ``thumbnail&extra=1`` would + inject additional query parameters into the upstream request.""" + config = OpenAIVideoConfig() + + url, _ = config.transform_video_content_request( + video_id="vid_123", + api_base="https://api.openai.com/v1/videos", + litellm_params=GenericLiteLLMParams(), + headers={}, + variant="thumbnail&extra=1", + ) + + # ``&`` and ``=`` must be percent-encoded so they cannot terminate + # the ``variant`` value or open a new query parameter. + assert "?variant=thumbnail%26extra%3D1" in url + # Sanity: the legitimate "thumbnail" value still round-trips cleanly. + url2, _ = config.transform_video_content_request( + video_id="vid_123", + api_base="https://api.openai.com/v1/videos", + litellm_params=GenericLiteLLMParams(), + headers={}, + variant="thumbnail", + ) + assert url2.endswith("?variant=thumbnail") + + def test_wrapped_character_id_is_decoded_then_encoded_as_path_segment(): config = OpenAIVideoConfig() character_id = encode_character_id_with_provider(