From 732190d71efedb54358bc753d715b6121839d8ae Mon Sep 17 00:00:00 2001 From: naaa760 Date: Fri, 3 Apr 2026 15:31:35 +0530 Subject: [PATCH] fix(proxy auth): normalize base-path routes to avoid false missing API key on public endpoints --- litellm/proxy/auth/auth_utils.py | 21 ++++++++++++------- .../proxy/auth/test_auth_utils.py | 17 +++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index d2f8320668e..c6623189405 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -298,13 +298,20 @@ def get_request_route(request: Request) -> str: remove base url from path if set e.g. `/genai/chat/completions` -> `/chat/completions """ try: - if hasattr(request, "base_url") and request.url.path.startswith( - request.base_url.path - ): - # remove base_url from path - return request.url.path[len(request.base_url.path) - 1 :] - else: - return request.url.path + request_path = request.url.path or "/" + base_path = request.base_url.path if hasattr(request, "base_url") else "" + + # If a base path prefix exists (e.g. "/genai"), strip it so route checks + # can match canonical proxy routes like "/chat/completions" and "/". + if base_path and base_path != "/" and request_path.startswith(base_path): + stripped_path = request_path[len(base_path) :] + if stripped_path == "": + return "/" + if not stripped_path.startswith("/"): + return f"/{stripped_path}" + return stripped_path + + return request_path except Exception as e: verbose_proxy_logger.debug( f"error on get_request_route: {str(e)}, defaulting to request.url.path={request.url.path}" diff --git a/tests/test_litellm/proxy/auth/test_auth_utils.py b/tests/test_litellm/proxy/auth/test_auth_utils.py index b66c081a943..806abd9ef98 100644 --- a/tests/test_litellm/proxy/auth/test_auth_utils.py +++ b/tests/test_litellm/proxy/auth/test_auth_utils.py @@ -9,6 +9,7 @@ from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.auth_utils import ( _get_customer_id_from_standard_headers, get_end_user_id_from_request_body, + get_request_route, get_model_from_request, get_key_model_rpm_limit, get_key_model_tpm_limit, @@ -252,6 +253,22 @@ def test_get_model_from_request_vertex_passthrough_still_works(): assert get_model_from_request(request_data={}, route=route) == "gemini-1.5-pro" +def test_get_request_route_strips_base_path_prefix(): + request = MagicMock() + request.url.path = "/genai/chat/completions" + request.base_url.path = "/genai" + + assert get_request_route(request) == "/chat/completions" + + +def test_get_request_route_returns_root_for_exact_base_path(): + request = MagicMock() + request.url.path = "/genai" + request.base_url.path = "/genai" + + assert get_request_route(request) == "/" + + def test_get_customer_user_header_returns_none_when_no_customer_role(): from litellm.proxy.auth.auth_utils import get_customer_user_header_from_mapping