From 792b6322cacc68a9e9cbfcce551af4e5e8e51957 Mon Sep 17 00:00:00 2001 From: fbossiere Date: Tue, 25 Aug 2026 09:29:59 +0200 Subject: [PATCH] test(utils): lock ssl_verify out of the provider request body ssl_verify configures the outbound TLS client, so the provider has no use for it. When it was not registered as a LiteLLM-level param the OpenAI param builder swept it into extra_body and sent it as a JSON body field, and an openai-compatible endpoint then rejected every request from a deployment carrying a private CA bundle path. The typed litellm_params refactor now derives all_litellm_params from ProviderConnection, which owns ssl_verify, so the one-line registry addition this PR originally carried is no longer needed and has been dropped on rebase. What is left is the regression test, which fails again as soon as ssl_verify stops being an owned kwarg. Fixes #38178 --- tests/unit/test_utils.py | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 2c612aa350c..3e2fe236a45 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -4968,6 +4968,70 @@ def test_client_side_timeout_marker_never_reaches_the_provider(): ) +def test_ssl_verify_never_reaches_the_provider(): + """ssl_verify configures the outbound TLS client, so it is meaningless to the provider. + Unregistered, the OpenAI param builder sweeps it into extra_body and it goes out as a + JSON body field: an openai-compatible endpoint then rejects the request, so a deployment + carrying a private CA bundle path cannot serve a single completion. + + Registering it keeps it on litellm_params, where the client config reads it from. + """ + ca_bundle = "/opt/app/certs/ca.crt" + kwargs = {"a_real_provider_specific_param": 1, "ssl_verify": ca_bundle} + + non_default = get_non_default_completion_params(dict(kwargs)) + + assert non_default == {"a_real_provider_specific_param": 1}, ( + f"ssl_verify leaked into the provider params: {sorted(set(non_default) - {'a_real_provider_specific_param'})}" + ) + assert "ssl_verify" in all_litellm_params + + assert ( + dict(GenericLiteLLMParams(model="openai/gpt-4o-mini", ssl_verify=ca_bundle)).get("ssl_verify") == ca_bundle + ), "registering ssl_verify must not strip it from litellm_params, which is where the TLS client reads it" + + +def test_completion_does_not_leak_ssl_verify_into_provider_request_body(): + mock_response = MagicMock() + mock_response.model_dump.return_value = { + "id": "chatcmpl-1", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4o-mini", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1, + "completion_tokens": 1, + "total_tokens": 2, + }, + } + + mock_raw_response = MagicMock() + mock_raw_response.headers = {} + mock_raw_response.parse.return_value = mock_response + + mock_client = MagicMock() + mock_client.chat.completions.with_raw_response.create.return_value = mock_raw_response + + litellm.completion( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "hi"}], + ssl_verify="/opt/app/certs/ca.crt", + api_key="sk-test", + client=mock_client, + ) + + create_kwargs = mock_client.chat.completions.with_raw_response.create.call_args.kwargs + assert "ssl_verify" not in create_kwargs + assert "ssl_verify" not in (create_kwargs.get("extra_body") or {}) + + class _RecordingDeploymentFailureLogger(CustomLogger): def __init__(self) -> None: super().__init__()