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