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.