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


##########
superset/charts/filters.py:
##########
@@ -111,6 +114,10 @@ def apply(self, query: Query, value: Any) -> Query:
         if security_manager.can_access_all_datasources():
             return query
 
+        # Embedded guests: scope to charts on the dashboards in their token.
+        if (guest_dashboards := guest_embedded_dashboard_filter()) is not None:
+            return query.filter(self.model.dashboards.any(guest_dashboards))

Review Comment:
   **Suggestion:** This guest branch is fail-open when a guest token has no 
dashboard resources (or produces no filter): it falls through to the regular 
viewer/dataset logic instead of denying access. For embedded guests, missing 
scoped dashboards should return no charts, otherwise guest visibility can 
expand based on role/dataset permissions rather than token scope. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Embedded guest can access charts beyond token dashboards scope.
   - ❌ MCP chart tools leak data via dataset-based access.
   - ⚠️ Guest token misconfig grants broader chart visibility than intended.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Configure an embedded guest token with EMBEDDED_SUPERSET enabled but no 
dashboard
   resources, mirroring tests/unit_tests/utils/filters_test.py:28-37 where 
guest.resources
   contains only a dataset entry and guest_embedded_dashboard_filter() returns 
None.
   
   2. Run the MCP service so embedded guest chart tools resolve charts via 
ChartDAO, which
   uses ChartFilter as its base_filter (superset/daos/chart.py:6-8 and 
get_by_id_or_uuid at
   lines 41-48).
   
   3. For this embedded guest, when ChartFilter.apply executes in
   superset/charts/filters.py:112-121, 
security_manager.can_access_all_datasources() is False
   and guest_embedded_dashboard_filter() returns None 
(superset/utils/filters.py:65-71 for
   empty ids), so the embedded-guest branch at lines 117-119 is skipped.
   
   4. ChartFilter.apply falls through to _apply_viewers() at lines 123-172, 
which applies
   subject-based viewer/editor filters and dataset access filters
   (get_dataset_access_filters), allowing charts reachable via dataset or 
subject permissions
   β€” including charts not on any dashboard in the guest token β€” to be listed or 
resolved by
   MCP tools such as list_charts 
(superset/mcp_service/chart/tool/list_charts.py:170-176) and
   get_chart_info (superset/mcp_service/chart/tool/get_chart_info.py:62-71), 
expanding
   embedded guest visibility beyond the token’s dashboard scope.
   ```
   </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=ab7828b0f81c47a0b033f3defb9391f8&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=ab7828b0f81c47a0b033f3defb9391f8&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/charts/filters.py
   **Line:** 118:119
   **Comment:**
        *Security: This guest branch is fail-open when a guest token has no 
dashboard resources (or produces no filter): it falls through to the regular 
viewer/dataset logic instead of denying access. For embedded guests, missing 
scoped dashboards should return no charts, otherwise guest visibility can 
expand based on role/dataset permissions rather than token scope.
   
   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%2F41753&comment_hash=25cc4f204074064ea8c7b98cb4ef2b58c2831187460b125c9a772afe47a11b29&reaction=like'>πŸ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41753&comment_hash=25cc4f204074064ea8c7b98cb4ef2b58c2831187460b125c9a772afe47a11b29&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/auth.py:
##########
@@ -236,32 +236,72 @@ def _log_scope_denial(
         )
 
 
-# Guest deny-list default (when MCP_GUEST_DENIED_TOOLS is unset); blocks tools
-# with no RBAC class that would otherwise fall open. Sync with mcp_config.py.
-_DEFAULT_GUEST_DENIED_TOOLS: frozenset[str] = frozenset(
-    {"find_users", "get_instance_info"}
+# Default-deny allow-list for embedded guests: a guest may call only these 
tools,
+# regardless of MCP_RBAC_ENABLED or how the guest role (PUBLIC_ROLE_LIKE) is
+# configured. Everything else is denied, including newly added tools until 
listed
+# here. Sync with mcp_config.py.
+_DEFAULT_GUEST_ALLOWED_TOOLS: frozenset[str] = frozenset(
+    {
+        # Dashboard structure, scoped to the token's embedded dashboards.
+        "get_dashboard_info",
+        "get_dashboard_layout",
+        "list_dashboards",
+        # Chart read + data, scoped by ChartFilter; data-model fields redacted.
+        "list_charts",
+        "get_chart_info",
+        "get_chart_data",
+        "get_chart_preview",
+    }
 )
 
 
-def _tool_denied_for_guest(func: Callable[..., Any]) -> bool:
-    """True when the current user is a guest and ``func`` is on the guest
-    deny-list β€” barring enumeration tools (user listing, instance metadata) 
that
-    may declare no RBAC class. ``isinstance`` (not ``is_guest_user``) keeps 
this
-    cheap and off the feature-flag path."""
-    if not isinstance(getattr(g, "user", None), GuestUser):
-        return False
-    denied = current_app.config.get("MCP_GUEST_DENIED_TOOLS")
-    # A str would make ``in`` do substring matching and corrupt the decision;
-    # require a real collection, else fall back to the default.
-    if not isinstance(denied, (set, frozenset, list, tuple)):
-        if denied is not None:
+def _guest_allowed_tools() -> frozenset[str]:
+    """The tool allow-list for embedded guests (``MCP_GUEST_ALLOWED_TOOLS`` or 
the
+    default). A str config is rejected β€” it would make ``in`` do substring
+    matching β€” and falls back to the safe default."""
+    allowed = current_app.config.get("MCP_GUEST_ALLOWED_TOOLS")
+    if not isinstance(allowed, (set, frozenset, list, tuple)):
+        if allowed is not None:
             logger.warning(
-                "MCP_GUEST_DENIED_TOOLS must be a set/list of tool names, got 
%s; "
-                "using the default deny-list",
-                type(denied).__name__,
+                "MCP_GUEST_ALLOWED_TOOLS must be a set/list of tool names, got 
%s; "
+                "using the default allow-list",
+                type(allowed).__name__,
             )
-        denied = _DEFAULT_GUEST_DENIED_TOOLS
-    return getattr(func, "__name__", None) in denied
+        return _DEFAULT_GUEST_ALLOWED_TOOLS
+    return frozenset(allowed)
+
+
+def _default_restricted_tool_policy(user: Any) -> frozenset[str] | None:
+    """Map a principal to its MCP tool allow-list, or None when the principal 
is
+    not allow-list-restricted (RBAC governs instead). Embedded guests are the
+    built-in restricted principal; override ``MCP_RESTRICTED_TOOL_POLICY`` to 
add
+    others (e.g. a future embedded principal type)."""
+    if isinstance(user, GuestUser):
+        return _guest_allowed_tools()
+    return None
+
+
+def _tool_denied_for_principal(func: Callable[..., Any]) -> bool:
+    """True when the current principal is allow-list-restricted and ``func`` is
+    not on its allow-list. Default-deny for restricted principals (embedded 
guests
+    by default), holds even with MCP_RBAC_ENABLED off; unrestricted principals 
are
+    governed by RBAC and never blocked here."""
+    policy = (
+        current_app.config.get("MCP_RESTRICTED_TOOL_POLICY")
+        or _default_restricted_tool_policy
+    )
+    allowed = policy(getattr(g, "user", None))

Review Comment:
   **Suggestion:** The restricted-policy config is invoked without verifying it 
is callable, so a misconfigured `MCP_RESTRICTED_TOOL_POLICY` (for example a 
list/dict/string) raises a `TypeError` and can break tool permission checks at 
runtime. Validate callability before invocation and fail closed with a 
controlled denial/log path instead of crashing. [type error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Misconfigured policy crashes MCP tool permission evaluation.
   - ❌ Tools/list endpoint fails when restricted policy misconfigured.
   - ⚠️ Operators lose predictable fail-closed behavior for restricted 
principals.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Misconfigure MCP_RESTRICTED_TOOL_POLICY in the Flask app config as a 
non-callable
   object (for example a list or string of tool names), even though it is 
intended to be a
   callable per superset/mcp_service/mcp_config.py:175.
   
   2. Trigger MCP tool visibility or permission evaluation so 
is_tool_visible_to_current_user
   in superset/mcp_service/auth.py:21-50 runs and calls 
_tool_denied_for_principal(tool_func)
   at line 29, or check_tool_permission in auth.py:88-132 calls
   _tool_denied_for_principal(func) at line 112.
   
   3. Inside _tool_denied_for_principal (superset/mcp_service/auth.py:65-85), 
policy is set
   from current_app.config.get("MCP_RESTRICTED_TOOL_POLICY") or
   _default_restricted_tool_policy at lines 70-72; because the configured value 
is a truthy
   non-callable, policy becomes that object rather than the default function.
   
   4. The next line, allowed = policy(getattr(g, "user", None)) at line 74, 
attempts to call
   the non-callable policy object, raising a TypeError that is not caught by
   _tool_denied_for_principal or by the surrounding try/except blocks in
   check_tool_permission (which only catches AttributeError, ValueError, 
RuntimeError at
   lines 220-222) and is_tool_visible_to_current_user (which catches 
AttributeError,
   RuntimeError, ValueError at lines 52-54), causing MCP tool permission checks 
and
   tools/list visibility evaluation to crash with an internal error instead of 
failing closed
   for the misconfigured principal.
   ```
   </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=8abbbb3db2d84090b5f8674a36f32487&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=8abbbb3db2d84090b5f8674a36f32487&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/mcp_service/auth.py
   **Line:** 289:293
   **Comment:**
        *Type Error: The restricted-policy config is invoked without verifying 
it is callable, so a misconfigured `MCP_RESTRICTED_TOOL_POLICY` (for example a 
list/dict/string) raises a `TypeError` and can break tool permission checks at 
runtime. Validate callability before invocation and fail closed with a 
controlled denial/log path instead of crashing.
   
   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%2F41753&comment_hash=25bd6888e61376e7d7472d3059b1e2947f1540b837283e5ff8c6094f2ff3e60d&reaction=like'>πŸ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41753&comment_hash=25bd6888e61376e7d7472d3059b1e2947f1540b837283e5ff8c6094f2ff3e60d&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