codeant-ai-for-open-source[bot] commented on code in PR #41120:
URL: https://github.com/apache/superset/pull/41120#discussion_r3507274208


##########
superset/models/helpers.py:
##########
@@ -1187,6 +1187,31 @@ def get_sqla_row_level_filters(
         # for datasources of type query
         return []
 
+    def _resolve_denylist_schema(self, sql: str) -> Optional[str]:
+        """
+        Resolve the effective default schema for ``DISALLOWED_SQL_TABLES``
+        checks, memoized per datasource instance.
+
+        An explicit datasource schema always wins. Otherwise the schema is
+        resolved through the query-aware ``get_default_schema_for_query`` (not
+        the static ``get_default_schema``) so the value matches what the engine
+        uses at runtime -- honoring dynamic-schema engines and URI/connect-arg
+        schema rules -- exactly like the SQL Lab / executor denylist gate.
+
+        The result is cached on the instance so adhoc-expression validation,
+        which runs once per selected column/metric/order-by, resolves the 
schema
+        at most once instead of issuing a fallback inspector round-trip per
+        expression.
+        """
+        if self.schema:
+            return self.schema

Review Comment:
   **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]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Datasource queries bypass Postgres search_path security gate.
   - ⚠️ Denylist behavior diverges between SQL Lab and datasource.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   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.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=0eb93242da834c44a776914482f03c99&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=0eb93242da834c44a776914482f03c99&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent πŸ€– </b></summary>
   
   ```mdx
   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
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=a8b3ebae3e83005645b80fd106500a0d4a7b575afc02b132df0889bafb80b701&reaction=like'>πŸ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=a8b3ebae3e83005645b80fd106500a0d4a7b575afc02b132df0889bafb80b701&reaction=dislike'>πŸ‘Ž</a>



##########
superset/models/helpers.py:
##########
@@ -1187,6 +1187,31 @@ def get_sqla_row_level_filters(
         # for datasources of type query
         return []
 
+    def _resolve_denylist_schema(self, sql: str) -> Optional[str]:
+        """
+        Resolve the effective default schema for ``DISALLOWED_SQL_TABLES``
+        checks, memoized per datasource instance.
+
+        An explicit datasource schema always wins. Otherwise the schema is
+        resolved through the query-aware ``get_default_schema_for_query`` (not
+        the static ``get_default_schema``) so the value matches what the engine
+        uses at runtime -- honoring dynamic-schema engines and URI/connect-arg
+        schema rules -- exactly like the SQL Lab / executor denylist gate.
+
+        The result is cached on the instance so adhoc-expression validation,
+        which runs once per selected column/metric/order-by, resolves the 
schema
+        at most once instead of issuing a fallback inspector round-trip per
+        expression.
+        """
+        if self.schema:
+            return self.schema
+        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

Review Comment:
   **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]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Postgres search_path gate skipped for later datasource queries.
   - ⚠️ Denylist table matching can use stale runtime schema.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   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.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=02442e14f0514d5ea8d0ef26909e7c9a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=02442e14f0514d5ea8d0ef26909e7c9a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent πŸ€– </b></summary>
   
   ```mdx
   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
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=95a51e5bc02640cea37b5bada9484a07ae26d56af65467f68929ac353d2e5862&reaction=like'>πŸ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=95a51e5bc02640cea37b5bada9484a07ae26d56af65467f68929ac353d2e5862&reaction=dislike'>πŸ‘Ž</a>



##########
superset/config.py:
##########
@@ -2048,6 +2059,30 @@ def engine_context_manager(  # pylint: 
disable=unused-argument
         "pg_stat_replication",
         "pg_stat_wal_receiver",
         "pg_user",
+        # The SQL-standard `information_schema` views expose table /
+        # column / privilege / view-definition metadata across the entire
+        # database role the connection user can see. Entries are
+        # schema-qualified so `check_tables_present` only matches when the
+        # reference resolves to `information_schema.<view>` -- either written
+        # explicitly or as an unqualified name under an `information_schema`
+        # search_path -- not any user table that happens to share a name.
+        "information_schema.tables",
+        "information_schema.columns",
+        "information_schema.schemata",
+        "information_schema.views",

Review Comment:
   **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]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ 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.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   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.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=31d49f19cedb427fbf7797864c7df204&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=31d49f19cedb427fbf7797864c7df204&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent πŸ€– </b></summary>
   
   ```mdx
   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
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=67a0078bd8a034181f4aae7be59343860a908535641df5a2f7041b73d67c3c45&reaction=like'>πŸ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41120&comment_hash=67a0078bd8a034181f4aae7be59343860a908535641df5a2f7041b73d67c3c45&reaction=dislike'>πŸ‘Ž</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to