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."""