diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index a21d2d81284..c2df5613e8e 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -11199,7 +11199,7 @@ class BaseLLMHTTPHandler: ) if extra_body: - data = _merge_extra_body_preserving_model(data, extra_body) + data.update(extra_body) ## LOGGING logging_obj.pre_call( @@ -11314,7 +11314,7 @@ class BaseLLMHTTPHandler: ) if extra_body: - data = _merge_extra_body_preserving_model(data, extra_body) + data.update(extra_body) ## LOGGING logging_obj.pre_call( diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index 64cc5860958..d88e0b173c1 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -272,6 +272,81 @@ async def test_async_response_api_handler_streams_when_provider_transform_adds_s assert client.post.call_args.kwargs["json"]["stream"] is True +def test_response_api_handler_extra_body_cannot_override_model(): + handler = BaseLLMHTTPHandler() + config = Mock() + config.validate_environment.return_value = {} + config.get_complete_url.return_value = "https://chatgpt.example.com/responses" + config.transform_responses_api_request.return_value = { + "model": "gpt-5.3-codex", + "input": "hi", + "stream": True, + } + config.sign_request.return_value = ({}, None) + client = HTTPHandler(client=httpx.Client()) + client.post = Mock( + return_value=httpx.Response( + 200, + request=httpx.Request("POST", "https://chatgpt.example.com/responses"), + ) + ) + logging_obj = Mock() + + handler.response_api_handler( + model="gpt-5.3-codex", + input="hi", + responses_api_provider_config=config, + response_api_optional_request_params={}, + custom_llm_provider="chatgpt", + litellm_params=GenericLiteLLMParams(), + logging_obj=logging_obj, + extra_body={"model": "attacker-model", "passthrough": "kept"}, + client=client, + ) + + sent_body = client.post.call_args.kwargs["json"] + assert sent_body["model"] == "gpt-5.3-codex" + assert sent_body["passthrough"] == "kept" + + +@pytest.mark.asyncio +async def test_async_response_api_handler_extra_body_cannot_override_model(): + handler = BaseLLMHTTPHandler() + config = Mock() + config.validate_environment.return_value = {} + config.get_complete_url.return_value = "https://chatgpt.example.com/responses" + config.transform_responses_api_request.return_value = { + "model": "gpt-5.3-codex", + "input": "hi", + "stream": True, + } + config.sign_request.return_value = ({}, None) + client = AsyncHTTPHandler() + client.post = AsyncMock( + return_value=httpx.Response( + 200, + request=httpx.Request("POST", "https://chatgpt.example.com/responses"), + ) + ) + logging_obj = Mock() + + await handler.async_response_api_handler( + model="gpt-5.3-codex", + input="hi", + responses_api_provider_config=config, + response_api_optional_request_params={}, + custom_llm_provider="chatgpt", + litellm_params=GenericLiteLLMParams(), + logging_obj=logging_obj, + extra_body={"model": "attacker-model", "passthrough": "kept"}, + client=client, + ) + + sent_body = client.post.call_args.kwargs["json"] + assert sent_body["model"] == "gpt-5.3-codex" + assert sent_body["passthrough"] == "kept" + + def test_merge_extra_body_preserving_model_blocks_model_override(): merged = _merge_extra_body_preserving_model( {"model": "authorized", "messages": []},