From c48723f21085312ae19df5603c8b494831538819 Mon Sep 17 00:00:00 2001 From: yassin Date: Tue, 15 Sep 2026 22:34:35 +0000 Subject: [PATCH] fix(proxy): let a db pass-through entry override the yaml entry on the same path Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 9 +++++- tests/test_litellm/proxy/test_proxy_server.py | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 7f90874e38a..8ce997eca8d 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -7081,9 +7081,16 @@ class ProxyConfig: ## PASS-THROUGH ENDPOINTS ## if "pass_through_endpoints" in _general_settings: db_pass_through_endpoints: Final = _general_settings["pass_through_endpoints"] + db_pass_through_paths: Final = frozenset( + endpoint.get("path") for endpoint in db_pass_through_endpoints if isinstance(endpoint, dict) + ) general_settings["pass_through_endpoints"] = [ *db_pass_through_endpoints, - *(config_passthrough_endpoints or []), + *( + endpoint + for endpoint in config_passthrough_endpoints or () + if endpoint.get("path") not in db_pass_through_paths + ), ] await initialize_pass_through_endpoints(pass_through_endpoints=db_pass_through_endpoints) diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 62c34738e51..3499a1ee1bc 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -7435,6 +7435,34 @@ async def test_update_general_settings_keeps_yaml_pass_through_endpoints_next_to assert still_protected.value.code == "401" +@pytest.mark.asyncio +async def test_update_general_settings_db_pass_through_endpoint_overrides_yaml_entry_on_the_same_path(): + """The auth check lets any matching ``auth: false`` entry through, so a DB + ``auth: true`` entry can only lock down a YAML-declared path if the YAML + entry is dropped from the merged list.""" + from litellm.proxy._types import ProxyException + from litellm.proxy.proxy_server import ProxyConfig + + yaml_endpoint: Final = {"path": "/v1/cuopt/request", "target": "https://example.com/post", "auth": False} + db_endpoint: Final = {"id": "db-1", "path": "/v1/cuopt/request", "target": "https://example.com/post", "auth": True} + + request: Final = MagicMock() + request.url.path = "/v1/cuopt/request" + request.headers = {} + request.query_params = {} + + settings: Final = patch("litellm.proxy.proxy_server.general_settings", {"pass_through_endpoints": [yaml_endpoint]}) # test-quality-ok: the method reads this module global; no injection seam + yaml_endpoints: Final = patch("litellm.proxy.proxy_server.config_passthrough_endpoints", [yaml_endpoint]) # test-quality-ok: module global holding the YAML endpoints the fix merges in + initialize: Final = patch("litellm.proxy.proxy_server.initialize_pass_through_endpoints", AsyncMock()) # test-quality-ok: route registration needs the FastAPI app; auth is the observable here + master_key: Final = patch("litellm.proxy.proxy_server.master_key", "sk-master") # test-quality-ok: a set master key is what makes a missing Authorization header a 401 + with settings, yaml_endpoints, initialize, master_key: + await ProxyConfig()._update_general_settings(db_general_settings={"pass_through_endpoints": [db_endpoint]}) + + with pytest.raises(ProxyException) as locked_down: + await user_api_key_auth(request=request, api_key=None) + assert locked_down.value.code == "401" + + def _fill_user_api_key_cache(cache: DualCache, count: int) -> None: for index in range(count): cache.set_cache(key=f"key-{index}", value={"token": f"key-{index}"}, local_only=True)