Skip to content

feat(sql): schema-qualified table denylist + information_schema/lo_* defaults#41120

Merged
sha174n merged 14 commits into
apache:masterfrom
sha174n:fix/sql-table-denylist-7.0
Jul 1, 2026
Merged

feat(sql): schema-qualified table denylist + information_schema/lo_* defaults#41120
sha174n merged 14 commits into
apache:masterfrom
sha174n:fix/sql-table-denylist-7.0

Conversation

@sha174n

@sha174n sha174n commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Split from #40421 (which keeps the non-breaking detection half). This is the table-denylist half, which carries a breaking default change, so it targets 7.0.

  • Adds schema-qualified matching for DISALLOWED_SQL_TABLES (so entries like information_schema.tables match an unqualified reference that resolves under that schema).
  • Expands the shipped defaults of DISALLOWED_SQL_TABLES / DISALLOWED_SQL_FUNCTIONS:
    • the information_schema.* views (table/column/privilege/view-definition metadata)
    • PostgreSQL large-object routines (lo_*)

These default additions reject queries that reference them where they were previously allowed, hence the 7.0 target. UPDATING.md documents the change and the per-deployment opt-out.

No associated ticket; this is the follow-up half of #40421.

Testing

pytest tests/unit_tests/sql/parse_tests.py

…defaults

This is the breaking-default half split out of apache#40421. It makes the SQL
table denylist schema-aware and rewires the call sites to report exactly
which denylisted tables were hit.

In superset/sql/parse.py:
- check_tables_present now distinguishes schema-qualified denylist
  entries (e.g. information_schema.tables, mysql.user, sys.sql_logins)
  from bare-name entries (e.g. pg_stat_activity): qualified entries only
  match qualified references, bare entries match by table name.
- new get_disallowed_tables returns the matched entries in their
  original denylist form, resolving unqualified references against the
  caller's default schema.
- new changes_search_path detects SET search_path / set_config rebinds
  so a script that changes the search path cannot bypass a qualified
  entry via an unqualified reference.

Call sites in models/helpers.py, sql/execution/executor.py, sql_lab.py
and commands/sql_lab/estimate.py are updated to pass the default schema
and surface the matched tables. New shipped defaults and the migration
note land in config.py and UPDATING.md.

Targeted at 7.0 because it changes default behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dosubot dosubot Bot added risk:breaking-change Issues or PRs that will introduce breaking changes sqllab Namespace | Anything related to the SQL Lab labels Jun 16, 2026
Comment thread UPDATING.md Outdated
@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.13043% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.42%. Comparing base (3651020) to head (e75c66c).

Files with missing lines Patch % Lines
superset/sql/parse.py 55.93% 17 Missing and 9 partials ⚠️
superset/sql/execution/executor.py 0.00% 10 Missing ⚠️
superset/models/helpers.py 76.47% 2 Missing and 2 partials ⚠️
superset/sql_lab.py 60.00% 2 Missing and 2 partials ⚠️
superset/commands/sql_lab/estimate.py 84.61% 1 Missing and 1 partial ⚠️
superset/models/core.py 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master   #41120   +/-   ##
=======================================
  Coverage   64.41%   64.42%           
=======================================
  Files        2668     2668           
  Lines      147198   147258   +60     
  Branches    33949    33965   +16     
=======================================
+ Hits        94824    94872   +48     
  Misses      50657    50657           
- Partials     1717     1729   +12     
Flag Coverage Δ
hive 39.07% <12.17%> (-0.03%) ⬇️
mysql 57.67% <57.39%> (+0.02%) ⬆️
postgres 57.73% <57.39%> (+0.01%) ⬆️
presto 40.62% <12.17%> (-0.03%) ⬇️
python 59.14% <58.26%> (+0.01%) ⬆️
sqlite 57.32% <23.47%> (-0.03%) ⬇️
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 thread superset/sql_lab.py Outdated
Comment thread superset/models/helpers.py
Comment thread superset/models/helpers.py

@bito-code-review bito-code-review Bot 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.

Code Review Agent Run #58aed7

Actionable Suggestions - 1
  • superset/config.py - 1
Additional Suggestions - 1
  • superset/commands/sql_lab/estimate.py - 1
    • CWE-287: Schema Resolution Bypasses Security Gate · Line 105-119
      Using `get_default_schema` instead of `get_default_schema_for_query` bypasses the Postgres `search_path` injection security gate that the RLS path runs. The comment on line 109 already acknowledges Postgres, and `config.py` lines 1979–1986 document that `information_schema.*` denylist entries are specifically designed to require this runtime resolution — but `get_default_schema` skips the `process_jinja_sql` + `search_path` scan that `get_default_schema_for_query` performs (`postgres.py` lines 617–628), creating a gap where a malicious `SET search_path` query bypasses the denylist check on the estimate path while being blocked on the execution path.
      Code suggestion
      --- a/superset/commands/sql_lab/estimate.py
      +++ b/superset/commands/sql_lab/estimate.py
       @@ -109,9 +109,14 @@ class QueryEstimationCommand:
                    # schema so an unqualified reference that the connection resolves to
                    # it at runtime (e.g. Postgres ``public``) still matches a qualified
                    # entry, even when the request omitted the schema.
      -            effective_schema = self._schema or self._database.get_default_schema(
      -                self._catalog or self._database.get_default_catalog()
      -            )
      +            # NOTE: Using get_default_schema_for_query (not get_default_schema)
      +            # to invoke engine-specific security gates, particularly the Postgres
      +            # search_path injection check that get_default_schema skips.
      +            _query = Query()
      +            _query.schema = self._schema or None
      +            _query.catalog = self._catalog
      +            _query.sql = sql_statement  # already available in scope
      +            effective_schema = self._database.get_default_schema_for_query(_query)
                    found_tables = parsed_script.get_disallowed_tables(
                        disallowed_tables, effective_schema
                    )
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/sql/parse.py - 1
Review Details
  • Files reviewed - 7 · Commit Range: 6e97e9f..6e97e9f
    • superset/commands/sql_lab/estimate.py
    • superset/config.py
    • superset/models/helpers.py
    • superset/sql/execution/executor.py
    • superset/sql/parse.py
    • superset/sql_lab.py
    • tests/unit_tests/sql/parse_tests.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ 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

Comment thread superset/config.py
sha174n and others added 2 commits June 16, 2026 18:56
get_disallowed_tables only indexed the schema.table form, so a
catalog-qualified denylist entry would never match. Index the
fully-qualified form too, and document that an unqualified reference
under an unknown default schema can only be matched via the
schema-indeterminate bare-name fallback (an inherent limit of static
analysis without the live search_path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- sql_lab.py: resolve the denylist effective schema through the
  query-aware get_default_schema_for_query, matching the RLS gate, so
  unqualified references match against the schema the engine actually
  resolves them to at runtime.
- models/helpers.py: fall back to the database default schema in both
  adhoc-expression and datasource denylist checks so a schema-qualified
  entry cannot be bypassed by an unqualified reference when the
  datasource has no schema (e.g. virtual datasets).
- config.py / UPDATING.md: add the lo_creat large-object alias to the
  PostgreSQL defaults and keep the migration note in sync (loread,
  lo_creat).

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

sha174n commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@bito-code-review Thanks. The lo_creat addition to DISALLOWED_SQL_FUNCTIONS['postgresql'] is in 22ad47d.

On the estimate-path schema suggestion: the script-level get_disallowed_tables already tracks schema_indeterminate across statements, so once a SET search_path appears it matches unqualified references against the bare-name portion of schema-qualified entries, the denylist is not bypassed on that path. The per-query search_path resolution via get_default_schema_for_query already runs on the RLS branch of the same command. I've kept the estimate denylist on get_default_schema to avoid building a transient probe Query on every estimate.

@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit c95c452
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a45334c6f8a2e0008e90a38
😎 Deploy Preview https://deploy-preview-41120--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.

Resolve the effective schema for the cost-estimate denylist check through
get_default_schema_for_query (the query-aware resolver used by the execution
path) instead of the static get_default_schema, so engine-specific per-query
gates run for the denylist check too. Share a single resolution between the
denylist check and RLS injection.

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

sha174n commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@bito-code-review Follow-up: on reflection I went ahead and switched the estimate denylist check to resolve the effective schema via get_default_schema_for_query too (sharing one resolution with the RLS branch, gated so the probe Query is only built when a denylist or RLS actually applies), so the per-query schema resolution runs on that path as well, matching sql_lab.execute_sql_statements. Done in 9d9856f.

@bito-code-review bito-code-review Bot 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.

Code Review Agent Run #0c2d63

Actionable Suggestions - 1
  • superset/sql/parse.py - 1
Review Details
  • Files reviewed - 5 · Commit Range: 6e97e9f..22ad47d
    • superset/sql/parse.py
    • tests/unit_tests/sql/parse_tests.py
    • superset/config.py
    • superset/models/helpers.py
    • superset/sql_lab.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ 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

Comment thread superset/sql/parse.py
sha174n and others added 2 commits June 17, 2026 00:13
- Resolve the per-query effective schema once in execute_sql_statements and
  share it between the denylist check and RLS injection (was resolved twice).
- Resolve the SQLExecutor path's schema through the query-aware
  get_default_schema_for_query, matching the SQL Lab and estimate paths so the
  denylist check and RLS injection use the schema the engine resolves at
  runtime and run the engine's per-query gates.
- Add get_disallowed_tables coverage for three-part (catalog.schema.table)
  denylist entries.

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

sha174n commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@bito-code-review Added the three-part (catalog.schema.table) matching test to test_get_disallowed_tables in f1259b6, a positive cat.sys.sql_logins match plus a differing-catalog negative case.

@bito-code-review

bito-code-review Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #7c4747

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset/sql_lab.py - 1
    • CWE-20: Unhandled Security Exception · Line 447-447
      Wrap the call to `database.get_default_schema_for_query(query)` in a try-except catching `SupersetSecurityException`, and convert it to an appropriate user-facing exception to prevent unhandled crashes.
  • superset/sql/execution/executor.py - 1
    • Missing unit tests for new method · Line 711-747
      The new `_resolve_query_schema` method has no dedicated unit tests. Existing test coverage mocks `get_default_schema_for_query` indirectly but doesn't test the new method's logic directly. Per adaptive rule [11730], new functionality requires tests covering success paths and edge cases.
Review Details
  • Files reviewed - 6 · Commit Range: 22ad47d..f1259b6
    • superset/commands/sql_lab/estimate.py
    • tests/unit_tests/commands/sql_lab/test_estimate.py
    • superset/sql/execution/executor.py
    • superset/sql_lab.py
    • tests/unit_tests/sql/execution/test_executor.py
    • tests/unit_tests/sql/parse_tests.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ 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

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

sha174n commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@bito-code-review Added dedicated unit tests for _resolve_query_schema (probe construction plus blank-schema handling) in 4617bdc. On the schema-resolution call in sql_lab.py: the exception it can raise is the intended per-query engine gate and is already handled by the SQL Lab error path, so it is deliberately propagated rather than swallowed.

Comment thread tests/unit_tests/commands/sql_lab/test_estimate.py
Comment thread superset/config.py
Comment thread superset/models/helpers.py Outdated
Comment thread superset/models/helpers.py Outdated
…* coverage

- Resolve the chart/adhoc-expression effective schema through the
  query-aware get_default_schema_for_query (memoized per datasource),
  matching the SQL Lab / executor gate and avoiding a per-expression
  inspector round-trip.
- Add lo_truncate/lo_truncate64 to the PostgreSQL function list and to
  the mutating-function detector; add the legacy lo_creat alias to the
  detector for parity with the function list.
- Add a docstring to the estimate denylist test and cover the new
  schema-resolution behaviour.

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

bito-code-review Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #1876d0

Actionable Suggestions - 0
Review Details
  • Files reviewed - 7 · Commit Range: f1259b6..102f2ec
    • tests/unit_tests/sql/execution/test_executor.py
    • superset/config.py
    • superset/models/helpers.py
    • superset/sql/parse.py
    • tests/unit_tests/commands/sql_lab/test_estimate.py
    • tests/unit_tests/models/helpers_test.py
    • tests/unit_tests/sql/parse_tests.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ 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

@durgaprasadml

Copy link
Copy Markdown
Contributor

Thanks for the work here. The overall approach makes sense to me, especially the move toward consistent denylist enforcement across SQL Lab, executor, estimation, and datasource validation paths, along with the schema-qualified matching support and expanded test coverage.

I noticed the transient Query construction + db.session.expunge(...) pattern now appears in multiple places (estimate.py, helpers.py, and executor.py) to resolve the effective schema through get_default_schema_for_query(). Would it make sense to centralize this logic into a shared helper/utility so future changes to query-aware schema resolution only need to be made in one place? It feels like these code paths are intentionally trying to maintain execution-parity, and a common helper could help prevent divergence over time.

Not a blocker—just wondering if there’s an opportunity to reduce duplication and keep the schema-resolution behavior synchronized across these security-sensitive paths.

Extract the transient-probe schema resolution shared by the SQL Lab
executor, the cost-estimate command, and datasource denylist checks into
a single Database.resolve_query_default_schema helper, and resolve the
schema consistently across the estimate and executor paths.

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

sha174n commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@durgaprasadml good call. Centralized the transient-probe schema resolution into a single Database.resolve_query_default_schema helper that the SQL Lab executor, the cost-estimate command, and the datasource denylist checks all delegate to, so the behavior stays in sync across these paths. Done in de1485c.

@sha174n

sha174n commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@bito-code-review on the flagged points: lo_creat is included in the DISALLOWED_SQL_TABLES defaults; the estimate path resolves the effective schema through the query-aware get_default_schema_for_query (now via the shared Database.resolve_query_default_schema, applied consistently on the executor path in de1485c); and three-part (catalog.schema.table) matching is covered by tests/unit_tests/sql/parse_tests.py::test_get_disallowed_tables (match + catalog-mismatch cases). The get_disallowed_tables signatures are consistent across the statement/script/Kusto variants — the script-level wrapper is intentionally two-arg and computes the per-statement flag internally before forwarding.

Comment thread tests/unit_tests/models/helpers_test.py Outdated
… search_path rejection

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

bito-code-review Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #6c9fc8

Actionable Suggestions - 0
Review Details
  • Files reviewed - 8 · Commit Range: 102f2ec..21a7362
    • superset/commands/sql_lab/estimate.py
    • superset/models/core.py
    • superset/models/helpers.py
    • superset/sql/execution/executor.py
    • tests/unit_tests/commands/sql_lab/test_estimate.py
    • tests/unit_tests/models/core_test.py
    • tests/unit_tests/sql/execution/test_executor.py
    • tests/unit_tests/models/helpers_test.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ 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

@rusackas

Copy link
Copy Markdown
Member

Still lookin' good to me, but needs a rebase, it seems.

…ylist-7.0

# Conflicts:
#	UPDATING.md
#	tests/unit_tests/models/core_test.py
@sha174n

sha174n commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@rusackas Merged latest master and resolved the conflicts (UPDATING.md and core_test.py) in 2f15332. Branch is back in sync and mergeable.

Resolve additive conflicts in UPDATING.md and helpers_test.py by keeping both
sides (our entries/tests plus upstream's). Suite green after merge.
Comment thread superset/models/core.py
Comment on lines +729 to +736
probe_query = Query(
database=self,
sql=sql,
schema=schema or None,
catalog=catalog,
client_id=utils.shortid()[:10],
user_id=utils.get_user_id(),
)

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: Add an explicit type annotation for the local query probe variable to satisfy the requirement for annotating relevant variables. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is new Python code introducing a local variable that can be explicitly annotated. The custom rule requires type hints for relevant variables where possible, so omitting an annotation here is a real violation.

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:** superset/models/core.py
**Line:** 729:736
**Comment:**
	*Custom Rule: Add an explicit type annotation for the local query probe variable to satisfy the requirement for annotating relevant variables.

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
👍 | 👎

Comment thread superset/sql_lab.py
if table.lower() in present:
found_tables.add(table)
raise SupersetDisallowedSQLTableException(found_tables or disallowed_tables)
rls_enabled = is_feature_enabled("RLS_IN_SQLLAB")

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: Add an explicit type annotation for this new boolean variable assignment. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This new local is a boolean-like value derived from a feature flag and can be annotated as bool, so omitting a type hint violates the rule requiring type hints on newly added Python variables that can be annotated.

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:** superset/sql_lab.py
**Line:** 436:436
**Comment:**
	*Custom Rule: Add an explicit type annotation for this new boolean variable assignment.

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
👍 | 👎

Comment thread superset/sql_lab.py
# schema the engine actually uses at runtime -- engines without dynamic-schema
# support ignore the request's selected schema -- so both controls match the
# execution path instead of a schema that may never apply.
effective_schema = ""

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: Add a concrete type hint for this new schema variable where it is first declared. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly introduced local variable initialized to a string and later reused as a schema name, so it is clearly annotatable as str; the lack of a type hint matches the custom rule violation.

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:** superset/sql_lab.py
**Line:** 445:445
**Comment:**
	*Custom Rule: Add a concrete type hint for this new schema variable where it is first declared.

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
👍 | 👎

Comment thread superset/sql_lab.py
Comment on lines +452 to +454
found_tables = parsed_script.get_disallowed_tables(
disallowed_tables, effective_schema
)

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: Add an explicit type annotation for this new variable holding parsed denylisted tables. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This new intermediate variable is introduced without an explicit type hint. Since it stores the result of a function call and is a relevant local variable that can be annotated, the suggestion correctly identifies a type-hint omission.

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:** superset/sql_lab.py
**Line:** 452:454
**Comment:**
	*Custom Rule: Add an explicit type annotation for this new variable holding parsed denylisted tables.

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
👍 | 👎

if self.schema:
return self.schema
if not hasattr(self, "_denylist_default_schema"):
catalog = self.catalog or self.database.get_default_catalog()

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: Add an explicit type annotation for the catalog local variable to satisfy the type-hint requirement for newly introduced variables. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly introduced Python local variable that can be annotated, but it is assigned without a type hint. That matches the custom rule requiring type hints for relevant new or modified variables.

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:** superset/models/helpers.py
**Line:** 1209:1209
**Comment:**
	*Custom Rule: Add an explicit type annotation for the `catalog` local variable to satisfy the type-hint requirement for newly introduced variables.

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
👍 | 👎

Comment on lines +1210 to +1212
self._denylist_default_schema = self.database.resolve_query_default_schema(
sql, None, catalog
)

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: Provide a type annotation for the cached _denylist_default_schema assignment so this new instance-level value is not introduced as implicitly typed. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a new instance attribute assignment in Python and it is not explicitly typed. Since the value is a schema string cache that can be annotated, the suggestion correctly identifies a type-hint omission.

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:** superset/models/helpers.py
**Line:** 1210:1212
**Comment:**
	*Custom Rule: Provide a type annotation for the cached `_denylist_default_schema` assignment so this new instance-level value is not introduced as implicitly typed.

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
👍 | 👎

Comment on lines +1275 to 1278
effective_schema = self._resolve_denylist_schema(sql_to_check)
found_tables = parsed.get_disallowed_tables(
disallowed_tables, effective_schema
)

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: Add explicit type annotations for the new effective_schema and found_tables locals in the disallowed-table validation path. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

Both locals are newly introduced and can be statically annotated, but they are not. This is a valid instance of the project rule requiring type hints on relevant new variables.

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:** superset/models/helpers.py
**Line:** 1275:1278
**Comment:**
	*Custom Rule: Add explicit type annotations for the new `effective_schema` and `found_tables` locals in the disallowed-table validation path.

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
👍 | 👎

Comment on lines +1516 to +1519
effective_schema = self._resolve_denylist_schema(sql)
found_tables = parsed_script.get_disallowed_tables(
disallowed_tables, effective_schema
)

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: Add explicit type annotations for the new effective_schema and found_tables locals in the query-level disallowed-table check. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

These are new local variables in Python code and they are left implicitly typed. They are suitable for annotation, so this is a real violation of the type-hint rule.

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:** superset/models/helpers.py
**Line:** 1516:1519
**Comment:**
	*Custom Rule: Add explicit type annotations for the new `effective_schema` and `found_tables` locals in the query-level disallowed-table check.

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
👍 | 👎

Comment on lines +1410 to +1412
resolve_mock = mocker.patch.object(
database, "get_default_schema_for_query", return_value="resolved_schema"
)

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: Add an explicit type annotation for this new mock variable to comply with the rule requiring type hints on relevant variables. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This new local variable is introduced without a type annotation, and it is a relevant variable that can be annotated under the Python type-hints rule. The suggestion matches an actual omission in the new code.

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/unit_tests/sql/execution/test_executor.py
**Line:** 1410:1412
**Comment:**
	*Custom Rule: Add an explicit type annotation for this new mock variable to comply with the rule requiring type hints on relevant variables.

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
👍 | 👎

)

executor = SQLExecutor(database)
options = QueryOptions(schema="explicit", template_params={"p": 1})

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: Add a type annotation for this newly introduced options variable so the new code consistently follows the type-hint requirement. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly introduced Python variable that could be type-annotated, and the code omits an explicit hint. That is a real violation of the type-hints rule.

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/unit_tests/sql/execution/test_executor.py
**Line:** 1415:1415
**Comment:**
	*Custom Rule: Add a type annotation for this newly introduced options variable so the new code consistently follows the type-hint requirement.

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
👍 | 👎

Comment on lines +1460 to +1470
gate = mocker.patch.object(
database,
"get_default_schema_for_query",
side_effect=SupersetSecurityException(
SupersetError(
message="blocked",
error_type=SupersetErrorType.QUERY_SECURITY_ACCESS_ERROR,
level=ErrorLevel.ERROR,
)
),
)

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: Annotate this new patched gate variable with an explicit type to satisfy the type-hints rule for relevant variables. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This patched object is a new local variable and it lacks a type annotation even though it could be annotated. The suggestion correctly identifies a type-hint omission.

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/unit_tests/sql/execution/test_executor.py
**Line:** 1460:1470
**Comment:**
	*Custom Rule: Annotate this new patched gate variable with an explicit type to satisfy the type-hints rule for relevant variables.

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
👍 | 👎

Resolve additive conflicts with apache#40499 (SQL_MAX_PARSE_LENGTH): keep both our
denylist entries/tests and upstream's parse-length gate. Suite green.
Comment thread superset/config.py
Comment on lines +2069 to +2072
"information_schema.tables",
"information_schema.columns",
"information_schema.schemata",
"information_schema.views",

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: Adding very common object names as schema-qualified denylist entries (for example information_schema.tables / information_schema.columns / information_schema.views) causes false positives when a query script changes search_path: the parser intentionally enters an indeterminate mode and matches unqualified names against the bare part of qualified denylist entries, so legitimate user tables named tables/columns/views can be blocked even outside information_schema. Either avoid shipping these generic entries by default, or tighten the matcher to only widen when the runtime search_path can actually resolve to information_schema. [logic error]

Severity Level: Major ⚠️
- ❌ SQL Lab blocks queries on user tables named tables.
- ⚠️ Postgres denylist misclassifies benign tables after search-path change.
- ⚠️ Behavior contradicts comments promising no overblocking of user tables.
Steps of Reproduction ✅
1. Configure a PostgreSQL database in Superset and ensure
`DISALLOWED_SQL_TABLES["postgresql"]` includes `information_schema.tables` (and related
views) as defined in `superset/config.py:2035-2114`, particularly lines 2069-2072 where
`"information_schema.tables"`, `"information_schema.columns"`, and
`"information_schema.views"` are added.

2. In that PostgreSQL database, create a user schema (for example `public`) containing a
normal user table named `tables` (or `columns`/`views`) and connect it to Superset; then,
from SQL Lab, run a multi-statement query like `SET search_path = public; SELECT * FROM
tables;` which is parsed into a `SQLScript` in `superset/sql_lab.py:420-459` (lines 2-3)
and passed to `parsed_script.get_disallowed_tables(disallowed_tables, effective_schema)`
at lines 33-35.

3. During parsing, `SQLScript.get_disallowed_tables` in `superset/sql/parse.py:1833-1912`
iterates statements; the first `SET search_path` causes
`SQLStatement.changes_search_path()` (implemented at `superset/sql/parse.py:1055-1115`,
lines 25-46) to return True, setting `schema_indeterminate = True` for subsequent
statements, after which the second `SELECT * FROM tables` is processed with
`schema_indeterminate` enabled.

4. For the second statement, `SQLStatement.get_disallowed_tables()` in
`superset/sql/parse.py:1144-1223` (lines 6-45) records the unqualified table `tables` in
`present_unqualified`, and because `schema_indeterminate` is True, it matches the bare
name `tables` against the qualified denylist entry `information_schema.tables` (lines
38-42), causing `parsed_script.get_disallowed_tables()` to return
`{"information_schema.tables"}`; `superset/sql_lab.py:420-459` then raises
`SupersetDisallowedSQLTableException` at lines 36-37, blocking the query even though it
only references the user table `public.tables`, demonstrating the overblocking described
in the suggestion.

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:** superset/config.py
**Line:** 2069:2072
**Comment:**
	*Logic Error: Adding very common object names as schema-qualified denylist entries (for example `information_schema.tables` / `information_schema.columns` / `information_schema.views`) causes false positives when a query script changes `search_path`: the parser intentionally enters an indeterminate mode and matches unqualified names against the bare part of qualified denylist entries, so legitimate user tables named `tables`/`columns`/`views` can be blocked even outside `information_schema`. Either avoid shipping these generic entries by default, or tighten the matcher to only widen when the runtime `search_path` can actually resolve to `information_schema`.

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
👍 | 👎

Comment on lines +1206 to +1207
if self.schema:
return self.schema

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 early return for an explicit schema bypasses resolve_query_default_schema, so engine-level per-query schema security checks (like rejecting SET search_path patterns) are never executed on this path. Keep the explicit schema as the final effective schema, but still invoke the query-aware resolver so security-gate side effects run consistently with SQL Lab/executor behavior. [security]

Severity Level: Critical 🚨
- ❌ Datasource queries bypass Postgres search_path security gate.
- ⚠️ Denylist behavior diverges between SQL Lab and datasource.
Steps of Reproduction ✅
1. Configure a PostgreSQL Database in Superset (engine `postgresql`) and set a
DISALLOWED_SQL_TABLES entry for that engine in `app.config`, e.g.
`"DISALLOWED_SQL_TABLES": {"postgresql": {"information_schema.tables"}}`, as read by
`_raise_for_disallowed_sql` in `superset/models/helpers.py:1497-1498`.

2. Create a SQLA dataset (SqlaTable) bound to this Database with an explicit `schema` (for
example `"analytics"`); SqlaTable inherits the helpers mixin so its `query()` method at
`superset/models/helpers.py:1491-1539` will call `self._raise_for_disallowed_sql(sql)`
before executing.

3. Execute a chart on this dataset whose underlying SQL contains a `SET search_path =
information_schema; SELECT * FROM tables` pattern (or any query that would trigger the
Postgres search_path gate); this SQL reaches `_raise_for_disallowed_sql(sql)` at
`superset/models/helpers.py:1491-1501`, which then calls `effective_schema =
self._resolve_denylist_schema(sql)` at line 1516.

4. Because the dataset has an explicit schema, `_resolve_denylist_schema` at
`superset/models/helpers.py:1190-1213` immediately returns `self.schema` via the `if
self.schema: return self.schema` branch (lines 1206-1207) and never invokes
`self.database.resolve_query_default_schema`. As a result,
`PostgresEngineSpec.get_default_schema_for_query` at
`superset/db_engine_specs/postgres.py:605-630` (which parses the query SQL and raises
`SupersetSecurityException` if `"search_path"` is present) is not executed on this
datasource path, even though the same gate does run unconditionally for SQL Lab
execution/estimate via `executor._resolve_query_schema`
(`superset/sql/execution/executor.py:440-452`) and `commands/sql_lab/estimate.py:106-113`.
The query therefore bypasses the per-query search_path security gate in the datasource
flow while being rejected in the SQL Lab/estimate flow.

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:** superset/models/helpers.py
**Line:** 1206:1207
**Comment:**
	*Security: The early return for an explicit schema bypasses `resolve_query_default_schema`, so engine-level per-query schema security checks (like rejecting `SET search_path` patterns) are never executed on this path. Keep the explicit schema as the final effective schema, but still invoke the query-aware resolver so security-gate side effects run consistently with SQL Lab/executor behavior.

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
👍 | 👎

Comment on lines +1208 to +1213
if not hasattr(self, "_denylist_default_schema"):
catalog = self.catalog or self.database.get_default_catalog()
self._denylist_default_schema = self.database.resolve_query_default_schema(
sql, None, catalog
)
return self._denylist_default_schema

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: This cache is stored once per datasource instance without keying by SQL/catalog, but schema resolution is query-dependent; after one call, later checks can reuse a stale schema for different SQL and skip a fresh resolver run. This can produce incorrect denylist matching and miss query-specific schema-gate exceptions, so cache by the relevant inputs (at minimum SQL and catalog) or avoid caching in this security-sensitive path. [cache]

Severity Level: Critical 🚨
- ❌ Postgres search_path gate skipped for later datasource queries.
- ⚠️ Denylist table matching can use stale runtime schema.
Steps of Reproduction ✅
1. Use a PostgreSQL Database with engine `postgresql` and configured DISALLOWED_SQL_TABLES
for that engine (read by `_process_sql_expression` and `_raise_for_disallowed_sql` in
`superset/models/helpers.py:1269-1273` and `1497-1498`). Ensure this Database is one where
per-query schema behavior matters (dynamic search_path / URI-based schema, as described by
`Database.resolve_query_default_schema` in `superset/models/core.py:701-734`).

2. Create a SqlaTable datasource against this Database with no explicit `schema` (so
`self.schema` is `None`), matching the test setup in
`tests/unit_tests/models/helpers_test.py:21-23`. Add multiple adhoc fields
(columns/metrics/order-bys) so `_process_sql_expression` at
`superset/models/helpers.py:1215-1281` is called repeatedly: the first call to
`_resolve_denylist_schema(sql_to_check)` will see no `_denylist_default_schema` attribute,
compute `catalog = self.catalog or self.database.get_default_catalog()` and invoke
`self.database.resolve_query_default_schema(sql, None, catalog)` (lines 1208-1212), which
builds a transient `Query` and calls `PostgresEngineSpec.get_default_schema_for_query` at
`superset/db_engine_specs/postgres.py:605-630`, running the search_path security gate and
resolving a schema. That schema is cached on the instance as `_denylist_default_schema`.

3. On the same datasource instance, issue a different query whose SQL now includes a
prohibited search_path change (e.g. `SET search_path = information_schema; SELECT * FROM
tables`) as part of the datasource’s base query or a saved query; when `query()` runs at
`superset/models/helpers.py:1491-1539`, it calls `_raise_for_disallowed_sql(sql)` (line
1534), which again computes `effective_schema = self._resolve_denylist_schema(sql)` at
line 1516.

4. Because `_denylist_default_schema` is already set from the earlier adhoc-expression
validation, `_resolve_denylist_schema` takes the cached branch at
`superset/models/helpers.py:1208-1213` and returns `self._denylist_default_schema` without
invoking `self.database.resolve_query_default_schema` for this new SQL. Consequently,
`PostgresEngineSpec.get_default_schema_for_query` (which parses the current query SQL and
raises `SupersetSecurityException` if `"search_path"` is present) is not run for the
executed query: the per-query gate and schema resolution are based solely on the first SQL
string that populated the cache (often a simple `SELECT <expression>`), not on the actual
query being validated/executed. This allows later queries on the same datasource instance
that change `search_path` or otherwise depend on per-query schema semantics to bypass the
engine-level security gate and can also cause denylist table matching to use a stale
runtime schema instead of the current one.

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:** superset/models/helpers.py
**Line:** 1208:1213
**Comment:**
	*Cache: This cache is stored once per datasource instance without keying by SQL/catalog, but schema resolution is query-dependent; after one call, later checks can reuse a stale schema for different SQL and skip a fresh resolver run. This can produce incorrect denylist matching and miss query-specific schema-gate exceptions, so cache by the relevant inputs (at minimum SQL and catalog) or avoid caching in this security-sensitive path.

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
👍 | 👎

Comment on lines +118 to +120
resolved_schema = self._database.resolve_query_default_schema(
self._sql, self._schema, catalog, self._template_params
)

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: This path resolves the effective schema from self._sql even though _apply_sql_security is validating the sql argument. When the argument has already been rendered/transformed, the schema/security gate may inspect different SQL than the denylist parser, producing inconsistent allow/deny behavior. Resolve the default schema from the same sql value being checked in this method. [security]

Severity Level: Major ⚠️
- ❌ SQL Lab cost estimate denylists may misfire under templating.
- ⚠️ RLS predicates may target wrong schema during estimation.
Steps of Reproduction ✅
1. Trigger SQL Lab cost estimation, which calls QueryEstimationCommand.run() in
superset/commands/sql_lab/estimate.py:76-100 (see BulkRead output where run() starts
around file line 145).

2. In run(), observe that `sql` is first set from `self._sql` and then, when
`self._template_params` is present, rendered via `template_processor.process_template(sql,
**self._template_params)` before being passed to `self._apply_sql_security(sql)`
(estimate.py:81-90).

3. Inside `_apply_sql_security` (superset/commands/sql_lab/estimate.py:80-74 relative to
file, shown at lines 11-74 in BulkRead), note that the denylist and RLS checks parse the
*rendered* SQL argument via `parsed_script = SQLScript(sql, engine=db_engine_spec.engine)`
(line ~20 in the excerpt).

4. In the same method, the effective schema used for denylist matching and RLS injection
is resolved via `resolved_schema = self._database.resolve_query_default_schema(self._sql,
self._schema, catalog, self._template_params)` (estimate.py:118-120), which builds a probe
Query using the original `self._sql` and re-renders templates inside
`get_default_schema_for_query` (superset/models/core.py:701-707). Because
`_apply_sql_security` validates the already-rendered `sql` argument while schema
resolution runs against a separate render of `self._sql`, any non-deterministic or
context-dependent templating (e.g. macros that vary per render) can make the
schema/security gate inspect different SQL than the denylist parser, causing inconsistent
allow/deny behavior for table denylists and RLS in the cost-estimation path.

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:** superset/commands/sql_lab/estimate.py
**Line:** 118:120
**Comment:**
	*Security: This path resolves the effective schema from `self._sql` even though `_apply_sql_security` is validating the `sql` argument. When the argument has already been rendered/transformed, the schema/security gate may inspect different SQL than the denylist parser, producing inconsistent allow/deny behavior. Resolve the default schema from the same `sql` value being checked in this method.

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
👍 | 👎

Comment on lines +452 to +453
resolved_schema = self._resolve_query_schema(sql, opts, catalog)
schema = opts.schema or resolved_schema

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 schema resolver is invoked with the pre-render SQL instead of the already rendered SQL that is actually parsed/executed. If templating is non-deterministic or context-dependent, the schema gate can evaluate a different statement than the one being checked for denylisted tables, causing security checks to run against the wrong schema context. Resolve the schema from the same rendered SQL string used to build the scripts so security checks and execution stay consistent. [security]

Severity Level: Critical 🚨
- ❌ Core SQL execution security may use mismatched schema context.
- ⚠️ Table denylists could be bypassed with non-deterministic templates.
Steps of Reproduction ✅
1. Execute a query through the core Database API, which uses SQLExecutor
(superset/sql/execution/executor.py:188-221) from superset/models/core.py:1438 to run
`Database.execute()` or `execute_async()`.

2. In SQLExecutor.execute() (executor.py:221-241), observe that `_prepare_sql(sql, opts)`
is called to render templates and build SQLScript objects, and the returned `schema` is
later passed into `self._check_security(transformed_script, schema)`
(executor.py:233-235).

3. Inside `_prepare_sql` (executor.py:426-443, BulkRead lines 9-43), note that
`rendered_sql = self._render_sql_template(sql, opts.template_params)` produces the SQL
text actually parsed and executed, and both `original_script` and `transformed_script` are
constructed from this `rendered_sql` via `SQLScript(rendered_sql, ...)`.

4. Still in `_prepare_sql`, see that `resolved_schema = self._resolve_query_schema(sql,
opts, catalog)` (executor.py:452) is invoked with the *original* pre-render SQL, not
`rendered_sql`. `_resolve_query_schema` delegates to
`self.database.resolve_query_default_schema(sql, opts.schema, catalog,
opts.template_params)` (executor.py:732-734), which builds a probe Query and calls
`get_default_schema_for_query` (superset/models/core.py:701-707), re-rendering templates
to determine the runtime default schema. Because security checks in `_check_security`
(executor.py:45-83) use `transformed_script` built from `rendered_sql` but the schema
context comes from a separate render of the original SQL, any non-deterministic or
context-dependent templating can make the denylisted table/function checks and DML guard
run against a schema derived from different SQL than the one actually being executed,
desynchronizing core execution security from the real query text.

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:** superset/sql/execution/executor.py
**Line:** 452:453
**Comment:**
	*Security: The schema resolver is invoked with the pre-render SQL instead of the already rendered SQL that is actually parsed/executed. If templating is non-deterministic or context-dependent, the schema gate can evaluate a different statement than the one being checked for denylisted tables, causing security checks to run against the wrong schema context. Resolve the schema from the same rendered SQL string used to build the scripts so security checks and execution stay consistent.

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
👍 | 👎

Comment thread superset/sql/parse.py
Comment on lines +1110 to +1116
parsed = self._parsed
if isinstance(parsed, exp.Command) and parsed.name.upper() == "SET":
tokens = str(parsed.expression).replace("=", " ").split()
while tokens and tokens[0].upper() in {"SESSION", "LOCAL"}:
tokens.pop(0)
return bool(tokens) and tokens[0].strip('"').lower() == "search_path"
return False

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 search-path change detector only handles SET/set_config forms and misses RESET search_path, which also changes how later unqualified tables resolve. In multi-statement scripts this can leave schema_indeterminate false and allow a schema-qualified denylist entry to be bypassed after a reset. Extend changes_search_path() to treat RESET search_path as a search-path mutation. [security]

Severity Level: Critical 🚨
- ❌ RESET search_path lets queries evade information_schema table denylists.
- ⚠️ Multi-statement SQLLab queries may bypass schema-qualified protections.
Steps of Reproduction ✅
1. Configure DISALLOWED_SQL_TABLES for a PostgreSQL engine to include a schema-qualified
entry such as `information_schema.tables`, as used by SQL Lab and the executor paths (see
usage in superset/sql_lab.py:452-456 and superset/sql/execution/executor.py:736-758 where
`parsed_script.get_disallowed_tables(...)` is called).

2. Submit a multi-statement script against that database via SQL Lab or
Database.execute(), for example `RESET search_path; SELECT * FROM tables;`, where
PostgreSQL resolves unqualified `tables` to `information_schema.tables` after the `RESET
search_path` statement based on the server’s default search_path.

3. Observe that the script is parsed into a SQLScript and checked for denylists using
`SQLScript.get_disallowed_tables(disallowed_tables, default_schema)`
(superset/sql/parse.py:1833-1859). This method iterates statements, passing a
`schema_indeterminate` flag that is flipped when `statement.changes_search_path()` returns
True, so later statements will conservatively match unqualified references against
schema-qualified denylist entries.

4. Inspect `SQLStatement.changes_search_path()` (superset/sql/parse.py:81-117). It returns
True for explicit `SET search_path` forms (via `get_settings()` and exp.Set) and for
`set_config('search_path', ...)`, and finally for opaque `SET ...` commands by checking
`parsed = self._parsed` and `isinstance(parsed, exp.Command) and parsed.name.upper() ==
"SET"` with a tokenized setting name. There is no handling for `RESET search_path`, which
SQLGlot parses as `exp.Command` named `"RESET"`, so `changes_search_path()` returns False
for that statement. As a result, `schema_indeterminate` never becomes True, and the
following `SELECT * FROM tables` is matched in `SQLStatement.get_disallowed_tables()` only
against `default_schema.tables`, not against the schema-qualified
`information_schema.tables` denylist entry. The script therefore passes denylist checks
while at runtime the unqualified `tables` actually resolves to the blocked
`information_schema.tables`, allowing `RESET search_path` to bypass schema-qualified table
denylists in multi-statement scripts.

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:** superset/sql/parse.py
**Line:** 1110:1116
**Comment:**
	*Security: The search-path change detector only handles `SET`/`set_config` forms and misses `RESET search_path`, which also changes how later unqualified tables resolve. In multi-statement scripts this can leave `schema_indeterminate` false and allow a schema-qualified denylist entry to be bypassed after a reset. Extend `changes_search_path()` to treat `RESET search_path` as a search-path mutation.

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
👍 | 👎

@sha174n
sha174n merged commit 2da2db6 into apache:master Jul 1, 2026
59 checks passed
@github-project-automation github-project-automation Bot moved this from Completed to Lazy Consensus Reached in Apache Superset 7.0 Jul 1, 2026
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

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

Labels

risk:breaking-change Issues or PRs that will introduce breaking changes size/XXL sqllab Namespace | Anything related to the SQL Lab

Projects

Status: Lazy Consensus Reached

Development

Successfully merging this pull request may close these issues.

3 participants