diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index e9066da2ea6..5d0c088d3d4 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -132,6 +132,13 @@ def get_key_models( return all_models +# Sentinel used by get_key_models_with_db_access_groups to signal "key has +# access_group_ids but none resolved to any model" so that get_complete_model_list() +# does not fall through to team_models (fail-closed). Callers that return model +# lists to end-users MUST strip this value before responding. +ACCESS_GROUP_NO_MODELS_SENTINEL = "__access_group_no_models__" + + async def get_key_models_with_db_access_groups( user_api_key_dict: UserAPIKeyAuth, proxy_model_list: List[str], @@ -168,7 +175,7 @@ async def get_key_models_with_db_access_groups( # Fail-closed: if the access group is configured but resolves to nothing # (deleted group, DB unavailable, or empty group), use a sentinel so # get_complete_model_list() does NOT fall through to team_models. - key_models = db_models if db_models else ["__access_group_no_models__"] + key_models = db_models if db_models else [ACCESS_GROUP_NO_MODELS_SENTINEL] return key_models diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 398022e01a2..058fbf1ab93 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -275,6 +275,7 @@ from litellm.proxy.auth.auth_utils import check_response_size_is_safe from litellm.proxy.auth.handle_jwt import JWTHandler from litellm.proxy.auth.litellm_license import LicenseCheck from litellm.proxy.auth.model_checks import ( + ACCESS_GROUP_NO_MODELS_SENTINEL, get_all_fallbacks, get_complete_model_list, get_key_models, @@ -10871,6 +10872,8 @@ async def model_info_v1( # noqa: PLR0915 infer_model_from_keys=general_settings.get("infer_model_from_keys", False), llm_router=llm_router, ) + # Strip the fail-closed sentinel — it must never appear in the /v1/models response. + all_models_str = [m for m in all_models_str if m != ACCESS_GROUP_NO_MODELS_SENTINEL] if len(all_models_str) > 0: _relevant_models = [] diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 28e246713b7..2e0f4f7d2bc 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -5516,6 +5516,7 @@ async def get_available_models_for_user( """ from litellm.proxy.auth.auth_checks import get_team_object from litellm.proxy.auth.model_checks import ( + ACCESS_GROUP_NO_MODELS_SENTINEL, get_complete_model_list, get_key_models_with_db_access_groups, get_team_models, @@ -5581,6 +5582,10 @@ async def get_available_models_for_user( only_model_access_groups=only_model_access_groups, ) + # Strip the fail-closed sentinel before returning to callers — it must never + # appear in a model listing response (e.g. GET /v1/models). + all_models = [m for m in all_models if m != ACCESS_GROUP_NO_MODELS_SENTINEL] + return all_models