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

diegopucci 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 dfdf8e75d8 fix: handle undefined template variables safely in query 
rendering. (#35009)
dfdf8e75d8 is described below

commit dfdf8e75d8d702d2db0c4fba8c5f6401e747d078
Author: Levis Mbote <[email protected]>
AuthorDate: Wed Jan 7 21:44:03 2026 +0300

    fix: handle undefined template variables safely in query rendering. (#35009)
---
 superset/jinja_context.py              | 19 ++++++++++--
 superset/sqllab/query_render.py        | 23 ++++++++++++++-
 tests/unit_tests/jinja_context_test.py | 54 ++++++++++++++++++++++++++++++++++
 3 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/superset/jinja_context.py b/superset/jinja_context.py
index 673e6d3815..dc9411030e 100644
--- a/superset/jinja_context.py
+++ b/superset/jinja_context.py
@@ -28,8 +28,8 @@ from typing import Any, Callable, cast, TYPE_CHECKING, 
TypedDict, Union
 import dateutil
 from flask import current_app, g, has_request_context, request
 from flask_babel import gettext as _
-from jinja2 import DebugUndefined, Environment, TemplateSyntaxError
-from jinja2.exceptions import SecurityError, UndefinedError
+from jinja2 import DebugUndefined, Environment, TemplateSyntaxError, 
UndefinedError
+from jinja2.exceptions import SecurityError
 from jinja2.sandbox import SandboxedEnvironment
 from sqlalchemy.engine.interfaces import Dialect
 from sqlalchemy.sql.expression import bindparam
@@ -65,6 +65,13 @@ if TYPE_CHECKING:
 
 logger = logging.getLogger(__name__)
 
+
+class UndefinedTemplateFunctionException(SupersetTemplateException):
+    """Raised when an undefined function-like Jinja identifier is 
encountered."""
+
+    pass
+
+
 NONE_TYPE = type(None).__name__
 ALLOWED_TYPES = (
     NONE_TYPE,
@@ -768,6 +775,14 @@ class BaseTemplateProcessor:
             raise SupersetTemplateException(
                 "Infinite recursion detected in template"
             ) from ex
+        except UndefinedError as ex:
+            match = re.search(r'["\']([^"\']+)["\']\s+is undefined', str(ex))
+            undefined_name = match.group(1) if match else None
+            if undefined_name and re.search(
+                r"\{\{\s*(?:[\w\.]*\.)?" + re.escape(undefined_name) + 
r"\s*\(", sql
+            ):
+                raise UndefinedTemplateFunctionException(str(ex)) from ex
+            raise
 
 
 class JinjaTemplateProcessor(BaseTemplateProcessor):
diff --git a/superset/sqllab/query_render.py b/superset/sqllab/query_render.py
index 7d41d7fb03..d8effce041 100644
--- a/superset/sqllab/query_render.py
+++ b/superset/sqllab/query_render.py
@@ -66,6 +66,23 @@ class SqlQueryRenderImpl(SqlQueryRender):
         except TemplateError as ex:
             self._raise_template_exception(ex, execution_context)
             return "NOT_REACHABLE_CODE"
+        except Exception as ex:
+            from superset.jinja_context import 
UndefinedTemplateFunctionException
+
+            if isinstance(ex, UndefinedTemplateFunctionException):
+                return query_model.sql.strip().strip(";")
+            raise
+
+    def _strip_sql_comments(
+        self,
+        execution_context: SqlJsonExecutionContext,
+        sql: str,
+    ) -> str:
+        from superset.sql.parse import SQLScript
+
+        engine = execution_context.query.database.db_engine_spec.engine
+        script = SQLScript(sql, engine)
+        return script.format(comments=False)
 
     def _validate(
         self,
@@ -74,7 +91,11 @@ class SqlQueryRenderImpl(SqlQueryRender):
         sql_template_processor: BaseTemplateProcessor,
     ) -> None:
         if is_feature_enabled("ENABLE_TEMPLATE_PROCESSING"):
-            syntax_tree = sql_template_processor.env.parse(rendered_query)
+            sql_for_validation = self._strip_sql_comments(
+                execution_context,
+                rendered_query,
+            )
+            syntax_tree = sql_template_processor.env.parse(sql_for_validation)
             undefined_parameters = find_undeclared_variables(syntax_tree)
             if undefined_parameters:
                 self._raise_undefined_parameter_exception(
diff --git a/tests/unit_tests/jinja_context_test.py 
b/tests/unit_tests/jinja_context_test.py
index 985dd909ee..929c470b31 100644
--- a/tests/unit_tests/jinja_context_test.py
+++ b/tests/unit_tests/jinja_context_test.py
@@ -1639,3 +1639,57 @@ def test_jinja2_server_error_handling(mocker: 
MockerFixture) -> None:
         assert "Internal Jinja2 template error" in str(exception)
         assert "MemoryError" in str(exception)
         assert "Out of memory" in str(exception)
+
+
+def test_undefined_template_function_exception(mocker: MockerFixture) -> None:
+    """Test UndefinedTemplateFunctionException for undefined function 
identifiers."""
+    from superset.jinja_context import (
+        BaseTemplateProcessor,
+        UndefinedTemplateFunctionException,
+    )
+
+    database = mocker.MagicMock()
+    database.db_engine_spec = mocker.MagicMock()
+
+    processor = BaseTemplateProcessor(database=database)
+
+    template = "SELECT {{ undefined_function() }}"
+    with pytest.raises(UndefinedTemplateFunctionException) as exc_info:
+        processor.process_template(template)
+
+    exception = exc_info.value
+    assert isinstance(exception, UndefinedTemplateFunctionException)
+    assert "undefined" in str(exception).lower()
+
+
+def test_undefined_template_function_exception_with_namespace(
+    mocker: MockerFixture,
+) -> None:
+    """Test namespaced undefined functions raise UndefinedError (not 
converted)."""
+    from jinja2.exceptions import UndefinedError
+
+    from superset.jinja_context import BaseTemplateProcessor
+
+    database = mocker.MagicMock()
+    database.db_engine_spec = mocker.MagicMock()
+
+    processor = BaseTemplateProcessor(database=database)
+    template = "SELECT {{ namespace.undefined_function() }}"
+    with pytest.raises(UndefinedError):
+        processor.process_template(template)
+
+
+def test_undefined_template_variable_not_function(mocker: MockerFixture) -> 
None:
+    """Test undefined variables with method calls raise UndefinedError."""
+    from jinja2.exceptions import UndefinedError
+
+    from superset.jinja_context import BaseTemplateProcessor
+
+    database = mocker.MagicMock()
+    database.db_engine_spec = mocker.MagicMock()
+
+    processor = BaseTemplateProcessor(database=database)
+
+    template = "SELECT {{ undefined_variable.some_method() }}"
+    with pytest.raises(UndefinedError):
+        processor.process_template(template)

Reply via email to