Skip to content

fix(sqllab): wrap process_template() in format_sql to prevent raw UndefinedError leak - #42917

Open
eschutho wants to merge 1 commit into
masterfrom
fix-format-sql-jinja-undefined-error
Open

fix(sqllab): wrap process_template() in format_sql to prevent raw UndefinedError leak#42917
eschutho wants to merge 1 commit into
masterfrom
fix-format-sql-jinja-undefined-error

Conversation

@eschutho

@eschutho eschutho commented Aug 8, 2026

Copy link
Copy Markdown
Member

SUMMARY

SqlLabRestApi.format_sql() (POST /api/v1/sqllab/format_sql/, SQL Lab's "Format SQL" button) calls process_template() with no catch for jinja2 exceptions. When an undefined Jinja variable is accessed via attribute/subscript (e.g. {{ tbl.name }}, not a bare {{ tbl }}), BaseTemplateProcessor.process_template()'s bare-raise fallback re-raises the raw jinja2.exceptions.UndefinedError instead of converting it to a Superset exception. Since neither of format_sql()'s existing except clauses (json.JSONDecodeError, and the outer ValidationError) catches it, the raw exception escapes to Flask's global @app.errorhandler(Exception) catch-all as an opaque 500 (GENERIC_BACKEND_ERROR) instead of a typed, user-actionable 4xx.

This is the same bug class fixed at 6 other process_template() call sites in this series: #42366, #42401, #42714, #42757, #42802, #42851. superset/sqllab/api.py had not previously been touched by any of them.

BEFORE/AFTER

Before: posting SQL with an undefined-attribute Jinja reference to /api/v1/sqllab/format_sql/ returns a 500 GENERIC_BACKEND_ERROR with a raw traceback-derived message.

After: the same request returns a 400 with a typed SupersetErrorException (GENERIC_COMMAND_ERROR) whose message names the undefined variable.

FIX

Added except TemplateError as ex: raise SupersetErrorException(SupersetError(message=str(ex), error_type=GENERIC_COMMAND_ERROR, level=ERROR), status=400) from ex after the existing except json.JSONDecodeError clause, mirroring QueryEstimationCommand.run() (#42757) exactly. SupersetErrorException is handled by the global @app.errorhandler(SupersetErrorException), the same mechanism estimate_query_cost() (a few lines above, same file) already relies on for its own SupersetErrorException — consistent with this file's existing pattern, not a new one. Additive-only; no existing behavior changes for well-formed templates.

TESTING INSTRUCTIONS

  1. POST /api/v1/sqllab/format_sql/ with sql: "select * from {{ tbl.name }}", template_params: "{}" (or any non-empty dict that doesn't define tbl), and a valid database_id.
  2. Before this fix: 500 with a raw jinja2 traceback message.
  3. After this fix: 400 with a structured error body naming tbl as undefined.

New regression test: test_format_sql_request_with_undefined_jinja_attribute in tests/integration_tests/sql_lab/api_tests.py, right after the existing test_format_sql_request_with_jinja. Confirmed it fails on pre-fix code (git stash on the source fix, raw UndefinedError traceback, assert rv.status_code == 400 fails with 500) and passes post-fix.

ADDITIONAL INFORMATION

Tradeoffs: none — this is an additive except clause converting an unhandled 500 into a typed 400; no existing success-path behavior changes.

Related: #42366, #42401, #42714, #42757, #42802, #42851 (same bug class, other process_template() call sites).

…efinedError leak (SC-116908)

format_sql() (POST /api/v1/sqllab/format_sql/) calls process_template()
with no catch for jinja2 exceptions. When an undefined Jinja variable is
accessed via attribute/subscript (e.g. {{ tbl.name }}), process_template()
re-raises the raw jinja2.exceptions.UndefinedError instead of converting
it, so it escapes to the global exception handler as an opaque 500
instead of a typed 4xx.

Mirrors QueryEstimationCommand.run() (#42757): catch TemplateError,
raise SupersetErrorException(status=400). Same bug class as #42366,
#42401, #42714, #42757, #42802, #42851 — 7th call site of
process_template()'s bare-raise fallback in this series.

Fixes SUPERSET-PYTHON (raw UndefinedError leak, format_sql call site)
@bito-code-review

bito-code-review Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #600332

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 9d633f3..9d633f3
    • superset/sqllab/api.py
    • tests/integration_tests/sql_lab/api_tests.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@dosubot dosubot Bot added api Related to the REST API global:jinja Related to Jinja templating sqllab Namespace | Anything related to the SQL Lab labels Aug 8, 2026
@netlify

netlify Bot commented Aug 8, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 9d633f3
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a775ab47b17ae00088fa94b
😎 Deploy Preview https://deploy-preview-42917--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.38%. Comparing base (fe06ebe) to head (9d633f3).
⚠️ Report is 29 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42917      +/-   ##
==========================================
+ Coverage   66.37%   66.38%   +0.01%     
==========================================
  Files        2857     2857              
  Lines      161048   161177     +129     
  Branches    37046    37074      +28     
==========================================
+ Hits       106892   107001     +109     
- Misses      52141    52150       +9     
- Partials     2015     2026      +11     
Flag Coverage Δ
hive 38.24% <60.00%> (-0.02%) ⬇️
mysql 57.79% <100.00%> (+0.04%) ⬆️
postgres 57.84% <100.00%> (+0.04%) ⬆️
presto 40.20% <60.00%> (-0.03%) ⬇️
python 59.24% <100.00%> (+0.04%) ⬆️
sqlite 57.46% <100.00%> (+0.04%) ⬆️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +358 to +360
assert rv.status_code == 400
assert "tbl" in resp_data["errors"][0]["message"]
assert "undefined" in resp_data["errors"][0]["message"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The regression test does not verify the required typed error contract. A different 400 response, such as a generic validation error containing the same message fragments, would pass these assertions even if error_type were no longer GENERIC_COMMAND_ERROR; assert the returned error type (and, if part of the contract, its level) explicitly. [api mismatch]

Severity Level: Minor 🧹
- ⚠️ Future error-type regressions can pass CI unnoticed.
- ⚠️ SQL Lab clients may receive an incorrect typed error contract.
- ⚠️ Current API handler explicitly promises GENERIC_COMMAND_ERROR responses.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/integration_tests/sql_lab/api_tests.py
**Line:** 358:360
**Comment:**
	*Api Mismatch: The regression test does not verify the required typed error contract. A different 400 response, such as a generic validation error containing the same message fragments, would pass these assertions even if `error_type` were no longer `GENERIC_COMMAND_ERROR`; assert the returned error type (and, if part of the contract, its level) explicitly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@bito-code-review

Copy link
Copy Markdown
Contributor

The review suggestion is correct. The current test only verifies the HTTP status code and message content, which could pass for other types of 400 errors. To enforce the typed error contract, you should explicitly assert the error_type and level in the response.

To resolve this, update the test assertion in tests/integration_tests/sql_lab/api_tests.py as follows:

        assert rv.status_code == 400
        assert resp_data["errors"][0]["error_type"] == "GENERIC_COMMAND_ERROR"
        assert resp_data["errors"][0]["level"] == "error"
        assert "tbl" in resp_data["errors"][0]["message"]
        assert "undefined" in resp_data["errors"][0]["message"]

There are no other comments on this PR to address.

tests/integration_tests/sql_lab/api_tests.py

assert rv.status_code == 400
        assert resp_data["errors"][0]["error_type"] == "GENERIC_COMMAND_ERROR"
        assert resp_data["errors"][0]["level"] == "error"
        assert "tbl" in resp_data["errors"][0]["message"]
        assert "undefined" in resp_data["errors"][0]["message"]

@eschutho
eschutho requested a review from rebenitez1802 August 8, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API global:jinja Related to Jinja templating preset-io size/M sqllab Namespace | Anything related to the SQL Lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant