You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/24 23:15:56 UTC

(superset) branch master updated: fix(mcp): fail closed when the JWT verifier has no pinned algorithm (#41296)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new f40abbbefd1 fix(mcp): fail closed when the JWT verifier has no pinned algorithm (#41296)
f40abbbefd1 is described below

commit f40abbbefd16a4e92671769f24d0bc4968992009
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Wed Jun 24 16:15:42 2026 -0700

    fix(mcp): fail closed when the JWT verifier has no pinned algorithm (#41296)
    
    Co-authored-by: Amin Ghadersohi <am...@gmail.com>
    Co-authored-by: Claude Fable 5 <no...@anthropic.com>
---
 superset/mcp_service/jwt_verifier.py              | 12 ++++++++++-
 tests/unit_tests/mcp_service/test_jwt_verifier.py | 25 +++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/superset/mcp_service/jwt_verifier.py b/superset/mcp_service/jwt_verifier.py
index 36fc9ff2197..74387c2b7b7 100644
--- a/superset/mcp_service/jwt_verifier.py
+++ b/superset/mcp_service/jwt_verifier.py
@@ -507,7 +507,17 @@ class DetailedJWTVerifier(MCPJWTVerifier):
                     _sanitize_for_log(token_alg),
                 )
                 return None
-            if self.algorithm and token_alg != self.algorithm:
+            # Require a pinned signing algorithm. Without one, the accepted
+            # algorithm family would be whatever the verification key or the
+            # underlying library permits; refuse rather than validating against
+            # an unconstrained algorithm set. The production factory always
+            # pins an algorithm, so this guards the directly-constructed case.
+            if not self.algorithm:
+                reason = "No signing algorithm pinned"
+                _jwt_failure_reason.set(reason)
+                logger.debug("Rejected token: verifier has no pinned signing algorithm")
+                return None
+            if token_alg != self.algorithm:
                 reason = "Algorithm mismatch"
                 _jwt_failure_reason.set(reason)
                 logger.debug(
diff --git a/tests/unit_tests/mcp_service/test_jwt_verifier.py b/tests/unit_tests/mcp_service/test_jwt_verifier.py
index 9dee7d7ff4c..e94c021573b 100644
--- a/tests/unit_tests/mcp_service/test_jwt_verifier.py
+++ b/tests/unit_tests/mcp_service/test_jwt_verifier.py
@@ -83,6 +83,31 @@ async def test_algorithm_mismatch(hs256_verifier):
     assert "HS256" not in reason
 
 
+@pytest.mark.asyncio
+async def test_unpinned_algorithm_is_rejected(
+    hs256_verifier: DetailedJWTVerifier,
+) -> None:
+    """A verifier with no pinned algorithm must reject signed tokens.
+
+    The upstream JWTVerifier currently always defaults the algorithm to RS256,
+    so this state is not reachable through normal construction. This asserts the
+    fail-closed guard so the verifier does not silently rely on that upstream
+    default: if the pinned algorithm is ever absent, tokens are rejected rather
+    than validated against an unconstrained algorithm family.
+    """
+    # Simulate an unpinned verifier (e.g. a future upstream default change).
+    hs256_verifier.algorithm = None
+    token = _make_token(
+        {"alg": "HS256", "typ": "JWT"},
+        {"sub": "user1", "iss": "test-issuer", "aud": "test-audience"},
+    )
+
+    result = await hs256_verifier.load_access_token(token)
+
+    assert result is None
+    assert _jwt_failure_reason.get() == "No signing algorithm pinned"
+
+
 @pytest.mark.asyncio
 async def test_malformed_token_header(hs256_verifier):
     """Token with invalid header should report malformed header."""