From b9f9f9034057e5b08bd8880eeb6de4d930d7ded7 Mon Sep 17 00:00:00 2001 From: Jonathan Wrede Date: Sun, 10 May 2026 18:31:19 +0000 Subject: [PATCH] fix(proxy): support wildcard patterns in JWT role_permissions.models can_rbac_role_call_model used a plain list membership check, so wildcard patterns like "bedrock-claude-*" or "*" in role_permissions models config were treated as literal strings and never matched concrete model names. Use fnmatch for pattern matching, consistent with how wildcards work in team/key model gating. Fixes #27536 --- litellm/proxy/auth/handle_jwt.py | 14 ++++---- .../test_user_api_key_auth.py | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index d1fd5818f35..a4cd5068a1f 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -874,13 +874,15 @@ class JWTAuthManager: if role_based_models is None or model is None: return True - if model not in role_based_models: - raise HTTPException( - status_code=403, - detail=f"Role={rbac_role} not allowed to call model={model}. Allowed models={role_based_models}", - ) + if model in role_based_models or any( + fnmatch.fnmatch(model, pattern) for pattern in role_based_models + ): + return True - return True + raise HTTPException( + status_code=403, + detail=f"Role={rbac_role} not allowed to call model={model}. Allowed models={role_based_models}", + ) @staticmethod def check_scope_based_access( diff --git a/tests/proxy_unit_tests/test_user_api_key_auth.py b/tests/proxy_unit_tests/test_user_api_key_auth.py index 210347aaf94..0df3fa6125b 100644 --- a/tests/proxy_unit_tests/test_user_api_key_auth.py +++ b/tests/proxy_unit_tests/test_user_api_key_auth.py @@ -1051,6 +1051,42 @@ def test_can_rbac_role_call_model_no_role_permissions(): ) +def test_can_rbac_role_call_model_wildcard(): + """Wildcard patterns in role_permissions.models should match model names.""" + from litellm.proxy.auth.handle_jwt import JWTAuthManager + from litellm.proxy._types import RoleBasedPermissions + + roles_based_permissions = [ + RoleBasedPermissions( + role=LitellmUserRoles.INTERNAL_USER, + models=["bedrock-claude-*"], + ), + RoleBasedPermissions( + role=LitellmUserRoles.PROXY_ADMIN, + models=["*"], + ), + ] + + assert JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings={"role_permissions": roles_based_permissions}, + model="bedrock-claude-draft-rep-sonnet", + ) + + assert JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.PROXY_ADMIN, + general_settings={"role_permissions": roles_based_permissions}, + model="any-model-name", + ) + + with pytest.raises(HTTPException): + JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings={"role_permissions": roles_based_permissions}, + model="openai-gpt-4", + ) + + @pytest.mark.parametrize( "route, request_data, expected_model", [