- Use superset.mcp_service.auth.has_request_context as patch target in
test_mcp_auth_hook_clears_stale_g_user tests; patching flask.has_request_context
has no effect on the module-level import already bound in auth.py
- Update test_jwt_access_token_skips_api_key_auth docstring to reference
API_KEY_PASSTHROUGH_CLAIM instead of the legacy _api_key_passthrough name
- Add noqa: BLE001 to broad exception catch in mcp_config.py to document
that the wide catch is intentional (JWT libs raise many types, secrets guard)
Three independent bugs let MCP requests presenting Bearer tokens with the
sst_ prefix authenticate as MCP_DEV_USERNAME without any validation under
streamable-http:
1. _resolve_user_from_api_key read the token from flask.request.headers,
but the streamable-http transport never pushes a Flask request context
— has_request_context() was always False, so the function returned
None before validating, falling through to the dev-user fallback.
Now reads the token from FastMCP's per-request AccessToken (which the
CompositeTokenVerifier already populated) and fails closed when the
key is invalid.
2. CompositeTokenVerifier was only installed when MCP_AUTH_ENABLED=True.
With FAB_API_KEY_ENABLED=True alone, no transport-level verifier
existed at all. The factory now builds an API-key-only verifier in
that case (jwt_verifier=None) that rejects non-API-key Bearer tokens
at the transport instead of silently accepting them.
3. The pass-through AccessToken was minted with scopes=[], which would
make FastMCP's RequireAuthMiddleware 403 every API-key request when
MCP_REQUIRED_SCOPES is non-empty. Pass-through now propagates
self.required_scopes.
Also addresses Daniel's review comment on superset/security/manager.py:
adds "ApiKey" to ADMIN_ONLY_VIEW_MENUS so the FAB ApiKeyApi PVMs are
gated to Admin instead of leaking to Alpha and Gamma.
Renames the pass-through claim from _api_key_passthrough to the
namespaced _superset_mcp_api_key_passthrough (exported as
API_KEY_PASSTHROUGH_CLAIM) so a custom claim from an external IdP can't
accidentally divert a JWT into the API-key validation path.
Tests updated to mock get_access_token instead of app.test_request_context
(the simulated Flask context was the reason the prior tests passed while
production failed). New tests cover API-key-only verifier mode, scope
propagation on pass-through, and the namespaced-claim isolation.
Wire CompositeTokenVerifier into create_default_mcp_auth_factory,
add _api_key_passthrough detection in _resolve_user_from_jwt_context,
create ApiKey permissions in create_custom_permissions, and update
test_auth_api_key with pass-through and non-matching prefix tests.