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
This commit is contained in:
fbossiere 2026-08-25 09:29:59 +02:00 • committed by François Bossière
parent b79fc9f1b0
commit 792b6322ca

View file

@ -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__()