From e7ff3e15673cfc056f396328971c6e68a1235133 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 22 Jun 2026 19:30:53 +0530 Subject: [PATCH] fix(proxy): gate include_fallback_errors behind expose_fallback_errors_to_caller setting Without an operator gate, any authenticated caller could set include_fallback_errors=True, trigger a fallback, and read raw upstream exception messages from the x-litellm-fallback-errors header and the litellm-fallback-metadata SSE event. Strip include_fallback_errors from request data in common_processing_pre_call_logic when expose_fallback_errors_to_caller is not set, so the router never builds the error list. Also gate _should_include_fallback_errors on the same setting as a secondary check for the streaming SSE injection path. --- litellm/proxy/common_request_processing.py | 2 ++ litellm/proxy/proxy_server.py | 2 ++ ...test_router_streaming_fallback_metadata.py | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 8ef931e8d25..8dec08460b4 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -1037,6 +1037,8 @@ class ProxyBaseLLMRequestProcessing: version=version, proxy_config=proxy_config, ) + if not general_settings.get("expose_fallback_errors_to_caller"): + self.data.pop("include_fallback_errors", None) if route_type in {"aresponses", "_aresponses_websocket"}: await _authorize_response_file_search_vector_stores( data=self.data, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 47976f11698..7dd7e6b0a93 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -7097,6 +7097,8 @@ def _is_positive_int_like(value: Any) -> bool: def _should_include_fallback_errors(request_data: dict[str, object]) -> bool: + if not general_settings.get("expose_fallback_errors_to_caller"): + return False return request_data.get("include_fallback_errors") is True diff --git a/tests/test_litellm/test_router_streaming_fallback_metadata.py b/tests/test_litellm/test_router_streaming_fallback_metadata.py index d2d742a7401..6ed70dc7cfe 100644 --- a/tests/test_litellm/test_router_streaming_fallback_metadata.py +++ b/tests/test_litellm/test_router_streaming_fallback_metadata.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock import pytest import litellm +from litellm.proxy.proxy_server import _should_include_fallback_errors from litellm.router import Router from litellm.router_utils.add_retry_fallback_headers import get_hidden_params_dict @@ -162,3 +163,25 @@ async def test_set_response_headers_adds_model_group_to_streaming_wrapper(): "x-existing": "keep", "x-litellm-model-group": "fallback-model", } + + +def test_should_include_fallback_errors_gated_by_operator_setting(): + request_data: dict = {"include_fallback_errors": True} + + import litellm.proxy.proxy_server as ps + + original = ps.general_settings.copy() if isinstance(ps.general_settings, dict) else {} + try: + ps.general_settings = {} + assert _should_include_fallback_errors(request_data) is False + + ps.general_settings = {"expose_fallback_errors_to_caller": False} + assert _should_include_fallback_errors(request_data) is False + + ps.general_settings = {"expose_fallback_errors_to_caller": True} + assert _should_include_fallback_errors(request_data) is True + + ps.general_settings = {"expose_fallback_errors_to_caller": True} + assert _should_include_fallback_errors({}) is False + finally: + ps.general_settings = original