From bcc3472a522c25dc59c15f0005f9ee6b88ecafce Mon Sep 17 00:00:00 2001 From: Jonathan Wrede Date: Sun, 10 May 2026 20:31:10 +0000 Subject: [PATCH] restrict wildcard matching to * patterns only fnmatch treats ? and [...] as wildcards, so a model alias like prod-[eu] would unintentionally match prod-e. Only apply fnmatch when the pattern contains *, treating all other entries as exact names. This matches the guard pattern used in guardrail_hooks. --- litellm/proxy/auth/handle_jwt.py | 4 ++- .../proxy/auth/test_handle_jwt.py | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index a4cd5068a1f..d5640146d8a 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -875,7 +875,9 @@ class JWTAuthManager: return True if model in role_based_models or any( - fnmatch.fnmatch(model, pattern) for pattern in role_based_models + fnmatch.fnmatch(model, pattern) + for pattern in role_based_models + if "*" in pattern ): return True diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 4ba39674476..16d2fcb5c03 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -2731,3 +2731,36 @@ class TestCanRbacRoleCallModelWildcard: model="openai-gpt-4", ) assert exc_info.value.status_code == 403 + + def test_fnmatch_metacharacters_treated_literally(self): + """Model names with ? or [...] are exact aliases, not wildcard patterns.""" + perms = [ + RoleBasedPermissions( + role=LitellmUserRoles.INTERNAL_USER, + models=["prod-[eu]", "model-v2?"], + ), + ] + # Exact match works + assert JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings=self._settings(perms), + model="prod-[eu]", + ) + assert JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings=self._settings(perms), + model="model-v2?", + ) + # fnmatch would have matched these, but they should be rejected + with pytest.raises(HTTPException): + JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings=self._settings(perms), + model="prod-e", + ) + with pytest.raises(HTTPException): + JWTAuthManager.can_rbac_role_call_model( + rbac_role=LitellmUserRoles.INTERNAL_USER, + general_settings=self._settings(perms), + model="model-v2x", + )