mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-25 05:54:53 +00:00
fix(proxy): reject adapter-backed pass-through requests once the route is deregistered
The URL-backed pass-through closure checks is_registered_pass_through_route() before dispatching, so a deleted endpoint 404s even though FastAPI has no route-removal API and the closure stays mounted. The adapter-backed closure skipped that check and dispatched unconditionally on its captured adapter_id, so a caller could keep invoking a deleted adapter endpoint (and its upstream credentials) indefinitely. Add the same liveness check to the adapter branch.
This commit is contained in:
parent
8221369688
commit
53bd60707a
2 changed files with 73 additions and 0 deletions
|
|
@ -1679,6 +1679,20 @@ def create_pass_through_route(
|
|||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
subpath: str = "", # captures sub-paths when include_subpath=True
|
||||
):
|
||||
from litellm.proxy.auth.auth_utils import ( # noqa: PLC0415
|
||||
get_request_route,
|
||||
)
|
||||
from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( # noqa: PLC0415
|
||||
InitPassThroughEndpointHelpers,
|
||||
)
|
||||
|
||||
path = get_request_route(request)
|
||||
if not InitPassThroughEndpointHelpers.is_registered_pass_through_route(route=path):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Pass-through endpoint {endpoint} not found. This could have been deleted or not yet added to the proxy.",
|
||||
)
|
||||
|
||||
return await chat_completion_pass_through_endpoint(
|
||||
fastapi_response=fastapi_response,
|
||||
request=request,
|
||||
|
|
|
|||
|
|
@ -1134,6 +1134,65 @@ def test_create_pass_through_route_reregistering_same_adapter_reuses_id():
|
|||
litellm.adapters = original_adapters
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_pass_through_route_adapter_dispatch_rejects_deregistered_route():
|
||||
"""
|
||||
Regression test: an adapter-backed pass-through route must stop
|
||||
dispatching once it's no longer registered (e.g. after
|
||||
delete_pass_through_endpoints removes it from the registry), the same
|
||||
way the URL-backed pass-through route already does. Without this check,
|
||||
a deleted adapter route stays reachable indefinitely because FastAPI has
|
||||
no built-in route removal and the adapter closure otherwise dispatches
|
||||
unconditionally on its captured adapter_id.
|
||||
"""
|
||||
original_adapters = litellm.adapters
|
||||
|
||||
class _DeregisteredAdapter(CustomLogger):
|
||||
pass
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
adapter = _DeregisteredAdapter()
|
||||
unique_path = "/test/path/unique/adapter_deregistered"
|
||||
|
||||
try:
|
||||
litellm.adapters = []
|
||||
|
||||
endpoint_func = create_pass_through_route(
|
||||
endpoint=unique_path,
|
||||
target=adapter,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.pass_through_endpoints.InitPassThroughEndpointHelpers.is_registered_pass_through_route"
|
||||
) as mock_is_registered,
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.pass_through_endpoints.chat_completion_pass_through_endpoint"
|
||||
) as mock_chat_completion,
|
||||
):
|
||||
mock_is_registered.return_value = False
|
||||
|
||||
mock_request = MagicMock(spec=Request)
|
||||
mock_request.scope = {"path": unique_path}
|
||||
mock_request.url = MagicMock()
|
||||
mock_request.url.path = unique_path
|
||||
mock_user_api_key_dict = MagicMock()
|
||||
mock_user_api_key_dict.api_key = "test-key"
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await endpoint_func(
|
||||
request=mock_request,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
fastapi_response=MagicMock(),
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 404
|
||||
mock_chat_completion.assert_not_called()
|
||||
finally:
|
||||
litellm.adapters = original_adapters
|
||||
|
||||
|
||||
def test_resolve_pass_through_request_timeout_precedence():
|
||||
assert resolve_pass_through_request_timeout(endpoint_timeout=900) == 900.0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue