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/17 16:33:17 UTC

(superset) branch fix/security-exception-detail-base-views updated: fix(security): avoid exception detail exposure in base views

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

rusackas pushed a commit to branch fix/security-exception-detail-base-views
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/fix/security-exception-detail-base-views by this push:
     new f255fe739e3 fix(security): avoid exception detail exposure in base views
f255fe739e3 is described below

commit f255fe739e343da1d96ccd30d899d535a037d5db
Author: Evan <ev...@preset.io>
AuthorDate: Wed Jun 17 09:31:43 2026 -0700

    fix(security): avoid exception detail exposure in base views
    
    Harden superset.views.base.get_error_msg so the full traceback is always
    logged server-side for operators, while the client response only ever
    carries the raw stacktrace when the operator explicitly opts in via the
    SHOW_STACKTRACE config (default off). Adds regression tests guarding the
    hidden-by-default, opt-in, and always-logged behaviors.
    
    Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
 superset/views/base.py              |  5 ++++
 tests/unit_tests/views/test_base.py | 58 +++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/superset/views/base.py b/superset/views/base.py
index 3220344013d..b692744246d 100644
--- a/superset/views/base.py
+++ b/superset/views/base.py
@@ -130,7 +130,12 @@ logger = logging.getLogger(__name__)
 
 
 def get_error_msg() -> str:
+    # Always preserve the full traceback server-side so operators retain it for
+    # debugging, independent of what is surfaced to the client.
+    logger.error("%s", traceback.format_exc())
     if app.config.get("SHOW_STACKTRACE"):
+        # Opt-in: operators can choose to expose the raw stacktrace in responses.
+        # This defaults to off so internal details are not leaked to clients.
         error_msg = traceback.format_exc()
     else:
         error_msg = "FATAL ERROR \n"
diff --git a/tests/unit_tests/views/test_base.py b/tests/unit_tests/views/test_base.py
index ed47188db73..c008dad2ddf 100644
--- a/tests/unit_tests/views/test_base.py
+++ b/tests/unit_tests/views/test_base.py
@@ -145,6 +145,64 @@ def test_locale_language_extraction_preserves_region_when_configured(
     assert _extract_language(locale_str, languages) == expected_language
 
 
+def test_get_error_msg_hides_stacktrace_by_default() -> None:
+    """``get_error_msg`` must not return the raw stacktrace to the client unless
+    the operator has explicitly opted in via ``SHOW_STACKTRACE``.
+
+    Regression guard for CodeQL ``py/stack-trace-exposure`` alerts: the default
+    response carries a generic message rather than internal exception detail.
+    """
+    from flask import current_app
+
+    from superset.views.base import get_error_msg
+
+    with patch.dict(current_app.config, {"SHOW_STACKTRACE": False}):
+        try:
+            raise RuntimeError("boom-secret-internal-detail")
+        except RuntimeError:
+            error_msg = get_error_msg()
+
+    assert "boom-secret-internal-detail" not in error_msg
+    assert "Traceback" not in error_msg
+    assert "Stacktrace is hidden" in error_msg
+
+
+def test_get_error_msg_exposes_stacktrace_when_opted_in() -> None:
+    """When ``SHOW_STACKTRACE`` is enabled the raw traceback is returned, matching
+    the documented opt-in debugging behavior."""
+    from flask import current_app
+
+    from superset.views.base import get_error_msg
+
+    with patch.dict(current_app.config, {"SHOW_STACKTRACE": True}):
+        try:
+            raise RuntimeError("boom")
+        except RuntimeError:
+            error_msg = get_error_msg()
+
+    assert "Traceback" in error_msg
+    assert "RuntimeError" in error_msg
+
+
+def test_get_error_msg_always_logs_full_traceback_server_side() -> None:
+    """The full traceback is always logged server-side, even when it is hidden
+    from the client response, so operators retain it for debugging."""
+    from flask import current_app
+
+    from superset.views.base import get_error_msg
+
+    with patch.dict(current_app.config, {"SHOW_STACKTRACE": False}):
+        with patch("superset.views.base.logger") as mock_logger:
+            try:
+                raise RuntimeError("boom-server-side")
+            except RuntimeError:
+                get_error_msg()
+
+    logged = " ".join(str(call) for call in mock_logger.error.call_args_list)
+    assert "Traceback" in logged
+    assert "boom-server-side" in logged
+
+
 def test_api_query_returns_json_content_type() -> None:
     """``Api.query`` returns a response with a JSON content type.