From 901d8cdd60f97c669720c19f39492fb6ed4670e8 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:41:04 -0500 Subject: [PATCH] Block HS* algorithms when JWTVerifier is configured with JWKS (#3419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Block HS* algorithms with JWKS in JWT verifier 🤖 Generated with GPT-5.2-Codex * Fix ruff format: remove extra blank line in test_supabase.py 🤖 Generated with Claude Code Co-authored-by: Jeremiah Lowin --------- Co-authored-by: Marvin Context Protocol <41898282+Marvin Context Protocol@users.noreply.github.com> Co-authored-by: Jeremiah Lowin --- src/fastmcp/server/auth/providers/jwt.py | 6 ++++++ tests/server/auth/providers/test_supabase.py | 11 ++++++++++- tests/server/auth/test_jwt_provider.py | 9 +++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/server/auth/providers/jwt.py b/src/fastmcp/server/auth/providers/jwt.py index 5640fa390..6368df12c 100644 --- a/src/fastmcp/server/auth/providers/jwt.py +++ b/src/fastmcp/server/auth/providers/jwt.py @@ -225,6 +225,12 @@ class JWTVerifier(TokenVerifier): }: raise ValueError(f"Unsupported algorithm: {algorithm}.") + if jwks_uri and algorithm.startswith("HS"): + raise ValueError( + "HMAC algorithms (HS256/HS384/HS512) require a shared secret via " + "public_key and cannot be used with jwks_uri" + ) + # Parse scopes if provided as string parsed_required_scopes = ( parse_scopes(required_scopes) if required_scopes is not None else None diff --git a/tests/server/auth/providers/test_supabase.py b/tests/server/auth/providers/test_supabase.py index 5182ca5e5..5d1ce06f9 100644 --- a/tests/server/auth/providers/test_supabase.py +++ b/tests/server/auth/providers/test_supabase.py @@ -93,7 +93,7 @@ class TestSupabaseProvider: @pytest.mark.parametrize( "algorithm", - ["HS256", "RS256", "ES256"], + ["RS256", "ES256"], ) def test_algorithm_configuration(self, algorithm): """Test that algorithm can be configured for different JWT signing methods.""" @@ -106,6 +106,15 @@ class TestSupabaseProvider: assert isinstance(provider.token_verifier, JWTVerifier) assert provider.token_verifier.algorithm == algorithm + def test_algorithm_hs256_rejected(self): + """Test that HS256 is rejected with Supabase JWKS verification.""" + with pytest.raises(ValueError, match="cannot be used with jwks_uri"): + SupabaseProvider( + project_url="https://abc123.supabase.co", + base_url="https://myserver.com", + algorithm="HS256", + ) + def test_algorithm_default_es256(self): """Test that algorithm defaults to ES256 when not specified.""" provider = SupabaseProvider( diff --git a/tests/server/auth/test_jwt_provider.py b/tests/server/auth/test_jwt_provider.py index 19b0f9370..9512cc1dc 100644 --- a/tests/server/auth/test_jwt_provider.py +++ b/tests/server/auth/test_jwt_provider.py @@ -201,6 +201,15 @@ class TestSymmetricKeyJWT: assert provider.algorithm == "HS256" assert provider.jwks_uri is None + def test_initialization_rejects_hs_algorithm_with_jwks_uri(self): + """Test that HMAC algorithms cannot be used with JWKS URI.""" + with pytest.raises(ValueError, match="cannot be used with jwks_uri"): + JWTVerifier( + jwks_uri="https://test.example.com/.well-known/jwks.json", + issuer="https://test.example.com", + algorithm="HS256", + ) + def test_initialization_with_different_symmetric_algorithms( self, symmetric_key_helper: SymmetricKeyHelper ):