Skip to content

fix(sqllab): require dataset match for raw query access - #40409

Merged
sha174n merged 31 commits into
apache:masterfrom
sha174n:fix/sqllab-dataset-scoped-access
Jun 2, 2026
Merged

sha174n merged 31 commits into
apache:masterfrom
sha174n:fix/sqllab-dataset-scoped-access

Conversation

@sha174n

@sha174n sha174n commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

raise_for_access in superset/security/manager.py previously had two related blind spots in its database/table/query branch:

  1. The SQL Lab path treated catalog_access / schema_access as sufficient to authorise any table in that catalog/schema, even when no Superset dataset existed for the table. The chart-data branch right below requires can_access_schema(datasource) on a registered SqlaTable, so the SQL Lab path was strictly broader than the chart-data path for the same permission.
  2. The MetaDB pseudo-database (superset/extensions/metadb.py) returns row data via the database + table form of raise_for_access and therefore inherited the same schema_access fallthrough.

This PR introduces an explicit kwarg force_dataset_match: bool = False on raise_for_access. When True, the historical catalog_access / schema_access fallthroughs are skipped and every referenced table must resolve to a Superset dataset the user has datasource_access on (or owns).

The three call sites that actually execute or return raw row data pass force_dataset_match=True:

  • superset/sqllab/validators.py (SQL Lab execute pre-flight)
  • superset/models/sql_lab.py::Query.raise_for_access (used by SQL Lab results, CSV export, and streaming export)
  • superset/extensions/metadb.py (MetaDB autoload path)

All other call sites keep the historical semantics unchanged. This preserves the documented schema_access behaviour for chart-data, dataset CRUD from SQL, /table_metadata/, /select_star/, explore on saved queries, and the like.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A. Backend permission scoping, no UI surface.

TESTING INSTRUCTIONS

pytest tests/integration_tests/security_tests.py::TestSecurityManager -k raise_for_access
pytest tests/unit_tests/security/manager_test.py

Three new tests under force_dataset_match=True:

  • test_raise_for_access_force_dataset_match_allows_dataset: a user with datasource_access on a registered SqlaTable can still query the table via the strict path.
  • test_raise_for_access_force_dataset_match_denies_schema_only: schema_access alone no longer satisfies the strict path.
  • test_raise_for_access_default_keeps_schema_access: the default (False) path still allows schema_access for non-dataset tables, preserving backwards compatibility for chart-data / dataset CRUD.

ADDITIONAL INFORMATION

Backwards compatibility

Deployments that used schema_access as a deliberate "give Gamma full SQL Lab access to one schema" grant will start being denied at the SQL Lab execute path, results-fetch, CSV export, streaming export, and MetaDB autoload. Mitigation:

  • Upgrade those users to database_access, or
  • Register the relevant tables as Superset datasets so datasource_access can be granted explicitly.

All non-SQL-Lab call sites (chart-data, dataset CRUD, table_metadata, select_star, explore, viz, query_context) keep the historical behaviour and are unaffected.

  • 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

In raise_for_access, the SQL Lab branch let catalog_access and
schema_access short-circuit the per-table dataset-existence check.
The chart-data branch right next door requires can_access_schema
on a registered SqlaTable, so the SQL Lab path was strictly broader
than the chart-data path for the same permission.

Tighten the SQL Lab path so every referenced table must resolve to
a Superset dataset the user holds datasource_access on (or owns).
The table-only call sites (/table_metadata/, /select_star/, MetaDB)
keep the historical catalog_access / schema_access fallthrough since
they return schema metadata only, not row data.

Two existing tests updated to mock query_datasources_by_name so they
exercise the new dataset-match path; one regression test added.

Backwards compatibility: deployments that used schema_access as a
deliberate "full SQL Lab access to one schema" grant will start
being denied. Mitigation: grant database_access, or register the
relevant tables as Superset datasets.

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

codecov Bot commented May 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.00%. Comparing base (41da35e) to head (a679aa3).

Files with missing lines Patch % Lines
superset/security/manager.py 90.90% 1 Missing and 1 partial ⚠️
superset/extensions/metadb.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40409      +/-   ##
==========================================
+ Coverage   63.94%   64.00%   +0.05%     
==========================================
  Files        2658     2658              
  Lines      143011   143035      +24     
  Branches    32866    32872       +6     
==========================================
+ Hits        91454    91546      +92     
+ Misses      49994    49907      -87     
- Partials     1563     1582      +19     
Flag Coverage Δ
hive 39.82% <39.39%> (+0.06%) ⬆️
mysql 58.52% <90.90%> (+0.12%) ⬆️
postgres 58.59% <90.90%> (+0.12%) ⬆️
presto 41.42% <39.39%> (+0.06%) ⬆️
python 60.07% <90.90%> (+0.11%) ⬆️
sqlite 58.21% <90.90%> (+0.07%) ⬆️
unit 100.00% <ø> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

…t_match)

Code review of the previous revision flagged that `query is not None`
was too broad as a discriminator: it caught chart-data on saved-query
datasources, dataset create/update from SQL, SQL Lab results/export,
and explore on saved queries, all of which were schema_access-by-design
flows. It also missed MetaDB, which uses the table-only call path but
actually returns row data.

Replace the inferred flag with an explicit kwarg
`force_dataset_match: bool = False` on `raise_for_access`. Default
(False) preserves the historical catalog_access / schema_access
fallthrough for chart-data, dataset CRUD, table_metadata, select_star,
explore, etc. The three call sites that actually execute or return raw
row data set it to True:

  - superset/sqllab/validators.py   (SQL Lab execute pre-flight)
  - superset/models/sql_lab.py      (Query.raise_for_access, used by
                                     results.py / export.py /
                                     streaming_export_command.py)
  - superset/extensions/metadb.py   (closes the same bypass on the
                                     database+table path)

Tests:
  - Reverts the broad updates to test_raise_for_access_query /
    test_raise_for_access_sql so they exercise the default path again.
  - Drops the vacuous regression test that never granted schema_access
    in the first place.
  - Adds three focused tests under force_dataset_match=True:
      * allows when a dataset exists and datasource_access matches
      * denies when only schema_access is held and no dataset exists
      * (default path) still allows schema_access for non-dataset
        tables, proving backwards compatibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels May 24, 2026
sha174n and others added 14 commits May 24, 2026 21:20
Code review on the previous revision flagged two related ways an
authenticated user could still defeat the new force_dataset_match
gate. Both close the same authorization gap and are confirmed.

1. SqlaTable.query_datasources_by_name drops the schema filter when
   schema is None (see connectors/sqla/models.py:1938 `if schema:`).
   On a backend whose default_schema_for_query() returns None and a
   parser output that did not include a schema, the lookup matches
   SqlaTables across every schema. The engine then resolves the
   actual query against the wrong schema via search_path, while the
   dataset-match check passed against an unrelated row.

   Fix: under force_dataset_match=True, refuse tables whose qualified
   schema is None. Users must pick a schema in the SQL Lab schema
   picker or qualify the table explicitly.

2. sqlglot represents statements it cannot fully model as exp.Command.
   extract_tables_from_statement returns set() for an exp.Command with
   no parseable inner literal (e.g. CALL stored_proc() that builds
   dynamic SQL internally). The per-table check below then sees zero
   tables and the script proceeds, even though the procedure may read
   arbitrary tables at execution time.

   Fix: add SQLScript.has_unparseable_statement; under
   force_dataset_match=True, refuse any script that contains one. The
   default (chart-data / dataset CRUD / metadata previews) is unchanged.

Two new regression tests cover both vectors. Existing tests for the
non-strict path are untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… + cover unparseable check

- security/manager.py: apply default schema in the table branch of
  raise_for_access so MetaDB's 2-part URIs (db.table) keep working
  under force_dataset_match; the strict-deny still fires when no
  default can be resolved.
- tests/integration_tests/security_tests.py: pin the new behavior.
- tests/unit_tests/sql/parse_tests.py: cover has_unparseable_statement
  to reach 100% sql/ coverage (parameterized: SELECT, CALL, mixed, KQL).
- tests/integration_tests/sqllab_tests.py: update test_sql_json_schema_access
  to reflect that schema_access alone no longer grants raw SQL Lab access
  (the legacy fallthrough this PR closes).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sha174n
sha174n marked this pull request as ready for review May 28, 2026 12:15
@dosubot dosubot Bot added authentication:access-control Rlated to access control risk:breaking-change Issues or PRs that will introduce breaking changes sqllab Namespace | Anything related to the SQL Lab labels May 28, 2026
@bito-code-review

bito-code-review Bot commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #4d0333

Actionable Suggestions - 0
Review Details
  • Files reviewed - 8 · Commit Range: 58c6272..ad12abf
    • superset/extensions/metadb.py
    • superset/models/sql_lab.py
    • superset/security/manager.py
    • superset/sql/parse.py
    • superset/sqllab/validators.py
    • tests/integration_tests/security_tests.py
    • tests/integration_tests/sqllab_tests.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

Comment thread tests/unit_tests/sql/parse_tests.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The provided pr_comments.csv file appears to be empty or contains only a header row without any actual review comments. As a result, I cannot validate or address the suggestion in the context of this PR. Please ensure the file contains the expected review comments for further analysis.

Comment thread superset/models/sql_lab.py
Comment thread superset/security/manager.py
sha174n and others added 2 commits May 30, 2026 00:01
Treat non-sqlglot statements as unparseable so engines without table
extraction (e.g. Kusto KQL) fail closed under force_dataset_match. Use
the rendered executed_sql during re-validation so result fetch and
export authorise against the SQL that actually ran.

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

bito-code-review Bot commented May 30, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #a8d3d7

Actionable Suggestions - 0
Filtered by Review Rules

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

  • superset/models/sql_lab.py - 1
Review Details
  • Files reviewed - 5 · Commit Range: ad12abf..6714ebf
    • superset/models/sql_lab.py
    • superset/security/manager.py
    • superset/sql/parse.py
    • tests/integration_tests/security_tests.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

sha174n and others added 4 commits May 30, 2026 06:01
The strict-scoping path in raise_for_access called
database.get_default_schema_for_query with the unrendered query.sql.
On the re-validation flow (results / CSV / streaming export) that
string still contains raw Jinja, so the Postgres engine spec's
search-path detection blew up on a SupersetParseError before the
executed_sql preference further down could take effect.

Route the rendered sql_for_parse through both the schema-resolution
call and the strict-mode parse, with template_params cleared when
the source is already rendered.

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

bito-code-review Bot commented May 30, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #9fd3ff

Actionable Suggestions - 0
Filtered by Review Rules

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

  • superset/security/manager.py - 1
Review Details
  • Files reviewed - 1 · Commit Range: 6714ebf..b9da6cb
    • superset/security/manager.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

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, but noting a couple of bot findings you may or may not want to address:

  1. EXPLAIN, SHOW, CALL, REFRESH parse as opaque commands → now denied for users who rely on datasource_access (not database_access), even on tables they can access. It's correct fail-closed behavior, but the backwards-compat notes don't mention it.
  2. Scope question: select_star/table_metadata previews were left on the old schema_access rules — but they also return real rows, so the same gap arguably persists there. Confirm that's intentionally out of scope vs. a follow-up.

@sha174n
sha174n merged commit 6eaee21 into apache:master Jun 2, 2026
58 of 59 checks passed
geido pushed a commit to geido/superset that referenced this pull request Oct 14, 2026
Under the strict dataset-match scoping used by SQL Lab raw queries,
MetaDB, and the MCP execute_sql tool (raise_for_access with
force_dataset_match=True, apache#40409), a referenced table was authorized if
the user had datasource_access on a registered dataset for it *or* was
an editor (owner) of such a dataset.

Accepting editorship on this path lets dataset creation stand in for
data access: any principal who can create datasets can make themselves
the editor of a dataset named after an arbitrary warehouse table and
then read its rows through raw SQL, and that access outlives revocation
of every real database/catalog/schema/datasource grant. SQL Lab,
MetaDB, and MCP execute_sql return raw rows, so they must follow the
user's live data grants.

Restrict the editorship accept in the per-table loop to the non-strict
callers (chart-data, dataset CRUD, /table_metadata/, /select_star/),
preserving the dataset-owner "explore your own dataset" grant (apache#20382)
there while requiring datasource_access on the strict path. Adds unit
and integration coverage asserting editorship alone is denied under
force_dataset_match while still allowed without it, and an UPDATING.md
note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authentication:access-control Rlated to access control risk:breaking-change Issues or PRs that will introduce breaking changes size/L sqllab Namespace | Anything related to the SQL Lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants