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/09 16:19:21 UTC

(superset) branch master updated: fix(mcp): return a generic message when a request is unauthenticated (#40861)

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 9105adc67b2 fix(mcp): return a generic message when a request is unauthenticated (#40861)
9105adc67b2 is described below

commit 9105adc67b2d8a15835fc1d87d3c1534dd6a48ad
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Tue Jun 9 09:19:15 2026 -0700

    fix(mcp): return a generic message when a request is unauthenticated (#40861)
    
    Co-authored-by: Claude Code <no...@anthropic.com>
---
 superset/mcp_service/auth.py                      | 23 +++++++----
 tests/unit_tests/mcp_service/test_auth_api_key.py | 50 +++++++++++++++++++++++
 2 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/superset/mcp_service/auth.py b/superset/mcp_service/auth.py
index 7c3be500cd7..e9d4e950a04 100644
--- a/superset/mcp_service/auth.py
+++ b/superset/mcp_service/auth.py
@@ -495,21 +495,26 @@ def get_user_from_request() -> User:
     if hasattr(g, "user") and g.user:
         return g.user
 
-    # No auth source available — log diagnostics server-side, raise generic
-    # client-facing error so no config details leak toward the client.
+    # No auth source available. Keep the configuration diagnostics in the
+    # server logs only -- the message returned to the (unauthenticated) client
+    # must not reveal which auth mechanisms are configured.
     auth_enabled = current_app.config.get("MCP_AUTH_ENABLED", False)
     jwt_configured = bool(
         current_app.config.get("MCP_JWKS_URI")
         or current_app.config.get("MCP_JWT_PUBLIC_KEY")
         or current_app.config.get("MCP_JWT_SECRET")
     )
-    logger.debug(
-        "No auth source found. "
-        "MCP_AUTH_ENABLED=%s, JWT keys configured=%s, "
-        "MCP_DEV_USERNAME configured=%s",
-        auth_enabled,
-        jwt_configured,
-        bool(current_app.config.get("MCP_DEV_USERNAME")),
+    details = [
+        f"No JWT access token in MCP request context "
+        f"(MCP_AUTH_ENABLED={auth_enabled}, "
+        f"JWT keys configured={jwt_configured})",
+        "No API key in Authorization header",
+        "MCP_DEV_USERNAME is not configured",
+        "g.user was not set by external middleware",
+    ]
+    logger.warning(
+        "MCP request could not be authenticated. Tried: %s",
+        "; ".join(details),
     )
     raise MCPNoAuthSourceError(
         "Authentication required. No valid credentials provided."
diff --git a/tests/unit_tests/mcp_service/test_auth_api_key.py b/tests/unit_tests/mcp_service/test_auth_api_key.py
index 31901a26f72..3b88b6b7424 100644
--- a/tests/unit_tests/mcp_service/test_auth_api_key.py
+++ b/tests/unit_tests/mcp_service/test_auth_api_key.py
@@ -23,6 +23,7 @@ The streamable-http transport does not push a Flask request context, so
 ``flask.request``. These tests mock ``get_access_token`` accordingly.
 """
 
+import logging
 from collections.abc import Generator
 from contextlib import contextmanager
 from unittest.mock import MagicMock, patch
@@ -183,6 +184,55 @@ def test_api_key_disabled_skips_auth(app: SupersetApp) -> None:
     mock_sm.validate_api_key.assert_not_called()
 
 
+@pytest.mark.usefixtures("_disable_api_keys")
+def test_unauthenticated_error_does_not_leak_config(
+    app: SupersetApp, caplog: pytest.LogCaptureFixture
+) -> None:
+    """The error returned to an unauthenticated client must not reveal which
+    auth mechanisms are configured; the diagnostics stay in the server log."""
+    # Snapshot and restore config: the ``app`` fixture is module-scoped, so
+    # leaving these set would leak into later tests in this module.
+    sentinel = object()
+    saved_auth = app.config.get("MCP_AUTH_ENABLED", sentinel)
+    saved_secret = app.config.get("MCP_JWT_SECRET", sentinel)
+    app.config["MCP_AUTH_ENABLED"] = True
+    app.config["MCP_JWT_SECRET"] = "super-secret-value"  # noqa: S105
+
+    try:
+        with app.test_request_context():
+            g.user = None
+            with caplog.at_level(logging.WARNING, logger="superset.mcp_service.auth"):
+                with pytest.raises(
+                    MCPNoAuthSourceError, match="Authentication required"
+                ) as exc_info:
+                    get_user_from_request()
+    finally:
+        for key, saved in (
+            ("MCP_AUTH_ENABLED", saved_auth),
+            ("MCP_JWT_SECRET", saved_secret),
+        ):
+            if saved is sentinel:
+                app.config.pop(key, None)
+            else:
+                app.config[key] = saved
+
+    message = str(exc_info.value)
+    assert message == "Authentication required. No valid credentials provided."
+    for leaked in (
+        "MCP_AUTH_ENABLED",
+        "JWT keys configured",
+        "API key",
+        "MCP_DEV_USERNAME",
+        "super-secret-value",
+    ):
+        assert leaked not in message
+
+    # The diagnostics the client must not see are still emitted server-side.
+    server_log = caplog.text
+    assert "could not be authenticated" in server_log
+    assert "MCP_AUTH_ENABLED=True" in server_log
+
+
 # -- No AccessToken -> API key auth skipped --