Skip to content

fix(tags): catch TemplateError when validating access for tagged SQL Lab queries - #43423

Open
eschutho wants to merge 2 commits into
masterfrom
fix-templateerror-tag-create-access
Open

fix(tags): catch TemplateError when validating access for tagged SQL Lab queries#43423
eschutho wants to merge 2 commits into
masterfrom
fix-templateerror-tag-create-access

Conversation

@eschutho

Copy link
Copy Markdown
Member

SUMMARY

When tagging a saved SQL Lab query, CreateCustomTagCommand._validate_object_access() calls security_manager.raise_for_access(query=target_object), wrapped only in except SupersetSecurityException:.

For a user who lacks blanket database-level access and relies on per-table/dataset permissions, raise_for_access() parses the query's Jinja-templated SQL via superset/sql/parse.py::process_jinja_sql() (which calls processor.env.parse(sql)) to determine referenced tables. As its docstring states, that can raise a raw jinja2.exceptions.TemplateError (e.g. TemplateSyntaxError on malformed Jinja such as an unclosed {% if %} block). Because that exception is not a SupersetSecurityException, it escaped the narrow except clause and propagated as an unhandled 500 instead of a proper Superset validation error.

Fix: widen the except clause in _validate_object_access to except (SupersetSecurityException, TemplateError):, keeping the file's existing append-to-exceptions-list convention, and reword the message slightly since a template-render failure isn't strictly an access denial.

PROBLEM

Tagging a saved SQL Lab query whose SQL contains malformed Jinja returned an unhandled 500 (raw jinja2.TemplateSyntaxError) rather than a TagInvalidError validation response, for users authorized via per-table/dataset permissions.

FIX

superset/commands/tag/create.py: catch TemplateError alongside SupersetSecurityException in CreateCustomTagCommand._validate_object_access, mapping the template-parse failure into the collected validation exceptions.

TESTING INSTRUCTIONS

  • Added test_validate_object_access_query_malformed_jinja in tests/unit_tests/tags/commands/create_test.py, which exercises the query path with malformed Jinja SQL (SELECT * FROM {% if %} broken) and forces the per-table authorization path (can_access_databaseFalse), asserting it surfaces as TagInvalidError.
  • Verified fail-before/pass-after: on the pre-fix code the test fails with the raw jinja2.exceptions.TemplateSyntaxError escaping through process_jinja_sql; post-fix it passes.
  • Full file green: pytest tests/unit_tests/tags/commands/create_test.py → 3 passed.
  • ruff check and ruff format --check pass on the changed files.

ADDITIONAL INFORMATION

This is the same recurring bug class as PRs #42366, #42401, #42757, #42917, #43145, and #43226 — a raw jinja2 TemplateError escaping a raise_for_access()/SQL-parsing call site instead of being mapped to a Superset exception. superset/commands/sql_lab/results.py already establishes the local pattern of catching TemplateError alongside SupersetSecurityException.

Note: the sibling site superset/commands/tag/delete.py has the identical duplicated _validate_object_access method with the same bug. It is intentionally left untouched here and will be addressed in a follow-up PR.

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

🤖 Generated with Claude Code

…Lab queries

When tagging a saved SQL Lab query, CreateCustomTagCommand._validate_object_access
calls security_manager.raise_for_access(query=...). For a user relying on
per-table/dataset permissions (no blanket database access), that path parses the
query's Jinja-templated SQL via process_jinja_sql(), which can raise a raw
jinja2 TemplateError (e.g. TemplateSyntaxError on malformed Jinja). The narrow
`except SupersetSecurityException:` let it escape as an unhandled 500. Widen the
except to also catch TemplateError so it surfaces as a validation error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dosubot dosubot Bot added the global:jinja Related to Jinja templating label Aug 22, 2026
@bito-code-review

bito-code-review Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #eb956f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 698181e..698181e
    • superset/commands/tag/create.py
    • tests/unit_tests/tags/commands/create_test.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 an incremental AI Review.

  • /review full - 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

@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.86%. Comparing base (f2610e9) to head (893925f).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43423   +/-   ##
=======================================
  Coverage   78.85%   78.86%           
=======================================
  Files        2876     2876           
  Lines      164581   164582    +1     
  Branches    38011    38011           
=======================================
+ Hits       129786   129791    +5     
+ Misses      32348    32344    -4     
  Partials     2447     2447           
Flag Coverage Δ
hive 38.08% <50.00%> (+<0.01%) ⬆️
mysql 57.77% <50.00%> (+<0.01%) ⬆️
postgres 57.80% <50.00%> (-0.01%) ⬇️
presto 40.01% <50.00%> (+<0.01%) ⬆️
python 83.54% <100.00%> (+<0.01%) ⬆️
sqlite 57.49% <50.00%> (+<0.01%) ⬆️
unit 73.58% <100.00%> (+0.01%) ⬆️

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.

)
)
except SupersetSecurityException:
except (SupersetSecurityException, TemplateError):

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 exception handling still omits SupersetParseError, which process_jinja_sql() raises when a saved query contains an unresolvable partition macro. That exception escapes _validate_object_access() instead of being appended to exceptions, so tagging such a query bypasses the command's TagInvalidError validation response. Catch SupersetParseError here as well and map it into the collected validation errors. [incomplete implementation]

Severity Level: Major ⚠️
- ❌ Tagging affected SQL Lab queries returns an unhandled server error.
- ⚠️ Tag API misses its expected validation response.
- ⚠️ Queries using dynamic partition macros cannot be tagged reliably.

Use CodeAnt Skill

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

**Path:** superset/commands/tag/create.py
**Line:** 102:102
**Comment:**
	*Incomplete Implementation: The exception handling still omits `SupersetParseError`, which `process_jinja_sql()` raises when a saved query contains an unresolvable partition macro. That exception escapes `_validate_object_access()` instead of being appended to `exceptions`, so tagging such a query bypasses the command's `TagInvalidError` validation response. Catch `SupersetParseError` here as well and map it into the collected validation errors.

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 flagged issue is correct. The current implementation in the PR catches TemplateError but misses SupersetParseError, which is specifically raised by process_jinja_sql() when partition macros cannot be resolved. To resolve this, you should update the exception handling in superset/commands/tag/create.py to include SupersetParseError in the except block.

Here is the corrected implementation:

from superset.exceptions import SupersetParseError
# ...
        except (SupersetSecurityException, TemplateError, SupersetParseError):
            exceptions.append(
                TagCreateFailedError(
                    f"Could not validate access for {object_type} {object_id}"
                )
            )

I have checked the available PR comments, and there are no other pending review comments to address. Would you like me to proceed with any other changes?

superset/commands/tag/create.py

except (SupersetSecurityException, TemplateError, SupersetParseError):
            exceptions.append(
                TagCreateFailedError(
                    f"Could not validate access for {object_type} {object_id}"
                )
            )

@eschutho
eschutho requested a review from rebenitez1802 August 22, 2026 16:56
…ency

The regression test let the real security_manager.raise_for_access(query=...)
run, which opens a live DB connection to introspect table-level perms before
reaching the Jinja parse. That only passed locally because a DB was up; the
unit_tests CI sandbox has no Postgres, so it failed there.

Mock raise_for_access directly to raise TemplateError instead. This still
proves what matters: except (SupersetSecurityException, TemplateError) in
create.py catches it and surfaces as TagInvalidError rather than escaping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@aminghadersohi aminghadersohi left a comment

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.

Reviewed the diff. Core fix is correct and the new test genuinely exercises the malformed-Jinja code path via raise_for_access -> process_jinja_sql. Two findings worth addressing before merge, plus one NIT.

)
)
except SupersetSecurityException:
except (SupersetSecurityException, TemplateError):

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.

No logging when swallowing TemplateError here. SupersetSecurityException being swallowed silently is intentional (routine, expected auth denial), but TemplateError represents a genuinely unexpected condition — previously it propagated as an unhandled 500 with a full traceback visible in server logs/Sentry. Now it's converted to a generic TagCreateFailedError with zero logger.warning/logger.exception call anywhere in this file. A malformed-Jinja saved query (or any other bug that raises TemplateError here) becomes invisible server-side. Suggest binding the exception (except (SupersetSecurityException, TemplateError) as ex:) and logging str(ex) before appending to exceptions.

)
)
except SupersetSecurityException:
except (SupersetSecurityException, TemplateError):

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.

The PR description says this follows the pattern already established in superset/commands/sql_lab/results.py, but that file actually uses two separate except arms with materially different handling:

except SupersetSecurityException as ex:
    raise SupersetErrorException(SupersetError(message=__("Cannot access the query"), ...), status=403) from ex
except TemplateError as ex:
    raise SupersetErrorException(SupersetError(message=str(ex), ...), status=400) from ex

It preserves the real Jinja error text (str(ex)) and distinguishes security vs. validation failures by status code. This PR instead merges both exception types into one except (...) tuple with a single hardcoded generic message, discarding str(ex) and the security/validation distinction the referenced pattern was built to keep. Worth at least surfacing str(ex) so the actual template error isn't lost.

)
)
except SupersetSecurityException:
except (SupersetSecurityException, TemplateError):

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.

NIT: this except wraps all four object_type branches (dashboard/chart/query/dataset), but only the query branch can currently raise TemplateError (confirmed by reading raise_for_access — the dashboard/chart/dataset branches never touch Jinja/process_jinja_sql). The comment above implies the fix is scoped to the query case; consider narrowing the except to just the query branch so a future unrelated TemplateError raised from another branch isn't silently misreported as an access-validation failure for that object type.

@bito-code-review

bito-code-review Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #31b26c

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 698181e..893925f
    • tests/unit_tests/tags/commands/create_test.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 an incremental AI Review.

  • /review full - 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

global:jinja Related to Jinja templating preset-io size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants