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.
This commit is contained in:
Sameer Kankute 2026-06-22 19:30:53 +05:30
parent da85887d64
commit e7ff3e1567
No known key found for this signature in database
3 changed files with 27 additions and 0 deletions

View file

@ -1037,6 +1037,8 @@ class ProxyBaseLLMRequestProcessing:
version=version, version=version,
proxy_config=proxy_config, 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"}: if route_type in {"aresponses", "_aresponses_websocket"}:
await _authorize_response_file_search_vector_stores( await _authorize_response_file_search_vector_stores(
data=self.data, data=self.data,

View file

@ -7097,6 +7097,8 @@ def _is_positive_int_like(value: Any) -> bool:
def _should_include_fallback_errors(request_data: dict[str, object]) -> 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 return request_data.get("include_fallback_errors") is True

View file

@ -4,6 +4,7 @@ from unittest.mock import MagicMock
import pytest import pytest
import litellm import litellm
from litellm.proxy.proxy_server import _should_include_fallback_errors
from litellm.router import Router from litellm.router import Router
from litellm.router_utils.add_retry_fallback_headers import get_hidden_params_dict 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-existing": "keep",
"x-litellm-model-group": "fallback-model", "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