From fb6d1dcf75dba01ba4c5be644201be2bdf30274b Mon Sep 17 00:00:00 2001 From: Praveena Ganesan Date: Wed, 26 Aug 2026 11:13:49 +0530 Subject: [PATCH] fix(proxy): ban ssl_verify as a request-body param Veria flagged that making ssl_verify functional (previous commit) also made it caller-controlled at the proxy: a client could send ssl_verify=false to disable upstream certificate verification, or a path string that reaches os.path.exists() as a local-file oracle. Add ssl_verify to _BANNED_REQUEST_BODY_PARAMS, same treatment as use_ssl. Legitimate per-deployment CA bundles still work via the existing admin opt-ins (allow_client_side_credentials proxy-wide, or configurable_clientside_auth_params per deployment); only caller- supplied request-body values are rejected. --- litellm/proxy/auth/auth_utils.py | 5 ++ .../proxy/auth/test_auth_utils.py | 69 +++++++++++++++++++ .../auth/test_banned_params_extra_body.py | 1 + 3 files changed, 75 insertions(+) diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index be65c3b39ec..2e633bdbb44 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -352,6 +352,11 @@ _BANNED_REQUEST_BODY_PARAMS: Final[tuple[str, ...]] = ( # the request away from the admin's pinned configuration. "nvcf_function_id", "use_ssl", + # TLS trust decision for the outbound provider connection. A caller-supplied + # value downgrades or disables certificate verification on a connection the + # admin pinned, and a string value reaches os.path.exists() as a local-file + # oracle. Deployment-level config only, same as ``use_ssl`` above. + "ssl_verify", # Per-deployment opt-in that hands the whole call to the Rust core. It is a # deployment decision, not a request one: the Rust path uses its own client # rather than the one the deployment configured, and reports no post_call, diff --git a/tests/test_litellm/proxy/auth/test_auth_utils.py b/tests/test_litellm/proxy/auth/test_auth_utils.py index cdf1f897707..eb04c95161f 100644 --- a/tests/test_litellm/proxy/auth/test_auth_utils.py +++ b/tests/test_litellm/proxy/auth/test_auth_utils.py @@ -2616,6 +2616,75 @@ class TestIsRequestBodySafeBlocksRivaUseSsl: ) +class TestIsRequestBodySafeBlocksSslVerify: + """``ssl_verify`` configures TLS trust for the outbound provider connection. + A caller-supplied ``false`` disables certificate verification on a + connection the admin pinned, and a string value reaches + ``os.path.exists()`` as a local-file oracle, so it is rejected as a + request-body param unless the admin opted in proxy-wide or + per-deployment, same as ``use_ssl`` above.""" + + def test_ssl_verify_false_in_request_body_is_rejected(self): + with pytest.raises(ValueError, match="ssl_verify"): + is_request_body_safe( + request_body={ + "model": "openai/gpt-3.5-turbo", + "ssl_verify": False, + }, + general_settings={}, + llm_router=None, + model="openai/gpt-3.5-turbo", + ) + + def test_ssl_verify_path_in_request_body_is_rejected(self): + with pytest.raises(ValueError, match="ssl_verify"): + is_request_body_safe( + request_body={ + "model": "openai/gpt-3.5-turbo", + "ssl_verify": "/etc/passwd", + }, + general_settings={}, + llm_router=None, + model="openai/gpt-3.5-turbo", + ) + + def test_admin_opt_in_proxy_wide_allows_ssl_verify(self): + assert ( + is_request_body_safe( + request_body={ + "model": "openai/gpt-3.5-turbo", + "ssl_verify": "/opt/app/certs/ca.crt", + }, + general_settings={"allow_client_side_credentials": True}, + llm_router=None, + model="openai/gpt-3.5-turbo", + ) + is True + ) + + def test_admin_opt_in_per_deployment_allows_ssl_verify(self, monkeypatch): + from litellm.proxy.auth import auth_utils + + monkeypatch.setattr( + auth_utils, + "_allow_model_level_clientside_configurable_parameters", + lambda model, param, request_body_value, llm_router: param == "ssl_verify", + ) + + assert ( + is_request_body_safe( + request_body={ + "model": "openai/gpt-3.5-turbo", + "ssl_verify": "/opt/app/certs/ca.crt", + }, + general_settings={}, + llm_router=None, + model="openai/gpt-3.5-turbo", + ) + is True + ) + + class TestIsRequestBodySafeBlocksBedrockTags: """``bedrock_tags`` lands as AWS resource tags on Bedrock batch jobs created with the proxy's AWS identity, so a caller-supplied value can diff --git a/tests/test_litellm/proxy/auth/test_banned_params_extra_body.py b/tests/test_litellm/proxy/auth/test_banned_params_extra_body.py index e87b206a40a..8e9ccd4a3b8 100644 --- a/tests/test_litellm/proxy/auth/test_banned_params_extra_body.py +++ b/tests/test_litellm/proxy/auth/test_banned_params_extra_body.py @@ -28,6 +28,7 @@ from litellm.proxy.auth.auth_utils import is_request_body_safe # noqa: E402 "base_url", "vertex_credentials", "azure_ad_token", + "ssl_verify", ], ) def test_banned_param_under_extra_body_is_rejected(banned_param):