You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/08/04 22:02:53 UTC
(superset) branch master updated: fix(sqllab): wrap process_template() in QueryEstimationCommand to prevent raw UndefinedError leak (#42757)
This is an automated email from the ASF dual-hosted git repository.
eschutho 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 4e9e884dd8c fix(sqllab): wrap process_template() in QueryEstimationCommand to prevent raw UndefinedError leak (#42757)
4e9e884dd8c is described below
commit 4e9e884dd8c9a86f75577c166f0ed87efc8b16c8
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Tue Aug 4 15:02:47 2026 -0700
fix(sqllab): wrap process_template() in QueryEstimationCommand to prevent raw UndefinedError leak (#42757)
---
superset/commands/sql_lab/estimate.py | 13 +++++++-
tests/unit_tests/commands/sql_lab/test_estimate.py | 38 ++++++++++++++++++++++
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/superset/commands/sql_lab/estimate.py b/superset/commands/sql_lab/estimate.py
index 7d7ef228c2e..fd68e201c63 100644
--- a/superset/commands/sql_lab/estimate.py
+++ b/superset/commands/sql_lab/estimate.py
@@ -21,6 +21,7 @@ from typing import Any, TypedDict
from flask import current_app as app
from flask_babel import gettext as __
+from jinja2.exceptions import TemplateError
from superset import db, is_feature_enabled, security_manager
from superset.commands.base import BaseCommand
@@ -150,7 +151,17 @@ class QueryEstimationCommand(BaseCommand):
sql = self._sql
if self._template_params:
template_processor = get_template_processor(self._database)
- sql = template_processor.process_template(sql, **self._template_params)
+ try:
+ sql = template_processor.process_template(sql, **self._template_params)
+ except TemplateError as ex:
+ raise SupersetErrorException(
+ SupersetError(
+ message=str(ex),
+ error_type=SupersetErrorType.GENERIC_COMMAND_ERROR,
+ level=ErrorLevel.ERROR,
+ ),
+ status=400,
+ ) from ex
# Apply the same SQL security controls used by the execution path
# (sql_lab.execute_sql_statements) so cost estimation cannot be used to
diff --git a/tests/unit_tests/commands/sql_lab/test_estimate.py b/tests/unit_tests/commands/sql_lab/test_estimate.py
index 61afd2bd97b..c59f2f7bc68 100644
--- a/tests/unit_tests/commands/sql_lab/test_estimate.py
+++ b/tests/unit_tests/commands/sql_lab/test_estimate.py
@@ -377,3 +377,41 @@ def test_apply_sql_security_propagates_engine_schema_gate(
# RLS injection must not happen once the schema gate has rejected the query.
mock_apply_rls.assert_not_called()
+
+
+# ---------------------------------------------------------------------------
+# process_template() error handling: raw jinja2 errors must not leak from run()
+# ---------------------------------------------------------------------------
+
+
+@patch("superset.commands.sql_lab.estimate.get_template_processor")
+@patch("superset.commands.sql_lab.estimate.security_manager", new_callable=MagicMock)
+@patch("superset.commands.sql_lab.estimate.db")
+def test_run_wraps_raw_jinja_undefined_error(
+ mock_db: MagicMock,
+ mock_security_manager: MagicMock,
+ mock_get_template_processor: MagicMock,
+) -> None:
+ """A raw jinja2 ``UndefinedError`` from ``process_template()`` (the
+ bare-raise fallback in ``BaseTemplateProcessor.process_template`` for
+ undefined attribute/subscript access, e.g. ``{{ foo.bar }}``) must not
+ leak past ``run()`` -- it should surface as a typed
+ ``SupersetErrorException``, matching the ``ExecuteSqlCommand`` sibling's
+ broad-catch pattern in ``commands/sql_lab/execute.py``."""
+ from jinja2.exceptions import UndefinedError
+
+ mock_database = MagicMock()
+ mock_db.session.query.return_value.get.return_value = mock_database
+ mock_security_manager.raise_for_access.return_value = None
+ mock_get_template_processor.return_value.process_template.side_effect = (
+ UndefinedError("'foo' is undefined")
+ )
+
+ command = QueryEstimationCommand(
+ _make_params(sql="SELECT {{ foo.bar }}", template_params={"x": 1})
+ )
+ with pytest.raises(SupersetErrorException) as exc_info:
+ command.run()
+
+ assert exc_info.value.status == 400
+ assert exc_info.value.error.error_type == SupersetErrorType.GENERIC_COMMAND_ERROR