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


##########
superset/datasets/api.py:
##########
@@ -855,6 +858,129 @@ def related_objects(self, id_or_uuid: str) -> Response:
             dashboards={"count": len(dashboards), "result": dashboards},
         )
 
+    @expose("/<id_or_uuid>/lineage", methods=("GET",))
+    @protect()
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: 
f"{self.__class__.__name__}.lineage",
+        log_to_statsd=False,
+    )
+    def lineage(self, id_or_uuid: str) -> Response:
+        """Get lineage information for a dataset.
+        ---
+        get:
+          summary: Get lineage information for a dataset
+          description: >-
+            Returns upstream (database) and downstream (charts, dashboards) 
lineage
+            information for a dataset
+          parameters:
+          - in: path
+            name: id_or_uuid
+            schema:
+              type: string
+            description: Either the id of the dataset, or its uuid
+          responses:
+            200:
+              description: Lineage information
+              content:
+                application/json:
+                  schema:
+                    $ref: "#/components/schemas/DatasetLineageResponseSchema"
+            401:
+              $ref: '#/components/responses/401'
+            404:
+              $ref: '#/components/responses/404'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        dataset = DatasetDAO.find_by_id_or_uuid(id_or_uuid)
+        if not dataset:
+            return self.response_404()
+
+        dataset_info = {
+            "id": dataset.id,
+            "name": dataset.name,
+            "database_id": dataset.database_id,
+            "database_name": (
+                dataset.database.database_name if dataset.database else None
+            ),
+            "schema": dataset.schema,
+            "table_name": dataset.table_name,
+        }
+
+        # Get upstream (database) information
+        upstream: dict[str, Any] = {}
+        if dataset.database:
+            upstream["database"] = {
+                "id": dataset.database.id,
+                "database_name": dataset.database.database_name,
+                "backend": dataset.database.backend,
+            }
+        else:
+            upstream["database"] = None
+
+        # Get downstream (charts and dashboards) information
+        related_data = DatasetDAO.get_related_objects(dataset.id)
+
+        # Build chart information with dashboard IDs, filtering both the charts
+        # and their linked dashboards by the current user's permissions so
+        # lineage never exposes assets the user cannot access.
+        charts = []
+        for chart in related_data["charts"]:
+            if not security_manager.can_access_chart(chart):
+                continue
+            dashboard_ids = [
+                d.id
+                for d in chart.dashboards
+                if security_manager.can_access_dashboard(d)
+            ]
+            charts.append(
+                {
+                    "id": chart.id,
+                    "slice_name": chart.slice_name,
+                    "viz_type": chart.viz_type,
+                    "dashboard_ids": dashboard_ids,
+                }
+            )
+
+        # Build dashboard information with chart IDs
+        dashboards = []
+        for dashboard in related_data["dashboards"]:
+            if not security_manager.can_access_dashboard(dashboard):
+                continue
+            chart_ids = [
+                chart.id
+                for chart in dashboard.slices
+                if chart.datasource_id == dataset.id

Review Comment:
   **Suggestion:** The dashboard payload includes `chart_ids` without checking 
chart-level permissions, so users who can view a dashboard but not all its 
charts can still discover restricted chart identifiers. Filter 
`dashboard.slices` with `security_manager.can_access_chart(...)` before adding 
IDs. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ❌ Dataset lineage exposes IDs of unauthorized charts.
   ⚠️ Dashboard viewers can infer restricted chart existence and identifiers.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure RBAC so that a user has access to a dashboard but not to all of 
its charts
   (for example, a chart owned by a different role or subject to dataset-level 
restrictions).
   SupersetSecurityManager exposes separate checks 
can_access_dashboard(dashboard) and
   can_access_chart(chart) at superset/security/manager.py:1288-1296 and 
1298-1307.
   
   2. Create a dataset (SqlaTable) that powers several charts (Slice rows) on a 
dashboard.
   DatasetDAO.get_related_objects(dataset.id) at 
superset/daos/dataset.py:124-144 will return
   charts filtered to Slice.datasource_id == dataset.id and 
Slice.datasource_type ==
   DatasourceType.TABLE, and dashboards that include at least one of those 
charts.
   
   3. As the partially-authorized user, call the dataset lineage endpoint GET
   /api/v1/dataset/<id_or_uuid>/lineage implemented at 
superset/datasets/api.py:861-869. The
   handler resolves the dataset via DatasetDAO.find_by_id_or_uuid(id_or_uuid) 
(line 897) and
   fetches related_data = DatasetDAO.get_related_objects(dataset.id) (line 924).
   
   4. In DatasetRestApi.lineage, dashboards are built at 
superset/datasets/api.py:948-964:
   the code filters dashboards with 
security_manager.can_access_dashboard(dashboard) (line
   950) but then computes chart_ids via a list comprehension over 
dashboard.slices using only
   chart.datasource_id == dataset.id (lines 952-955), omitting a
   security_manager.can_access_chart(chart) check. As a result, chart_ids 
includes
   identifiers for restricted charts whose can_access_chart would be False for 
this user,
   leaking their existence and IDs in the downstream.dashboards[].chart_ids 
portion of the
   DatasetLineageResponseSchema (superset/datasets/schemas.py:275-280).
   ```
   </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=0c55e89e46154d5fae49533fe491e02c&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=0c55e89e46154d5fae49533fe491e02c&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/datasets/api.py
   **Line:** 952:955
   **Comment:**
        *Security: The dashboard payload includes `chart_ids` without checking 
chart-level permissions, so users who can view a dashboard but not all its 
charts can still discover restricted chart identifiers. Filter 
`dashboard.slices` with `security_manager.can_access_chart(...)` before adding 
IDs.
   
   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%2F40912&comment_hash=66d533d1c3728c57b66345d684df46756a182faf84e15ebe9eb7de8fb3163428&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40912&comment_hash=66d533d1c3728c57b66345d684df46756a182faf84e15ebe9eb7de8fb3163428&reaction=dislike'>👎</a>



##########
superset/dashboards/schemas.py:
##########
@@ -633,3 +633,60 @@ class CacheScreenshotSchema(Schema):
         fields.List(fields.Str(), validate=lambda x: len(x) == 2), 
required=False
     )
     permalinkKey = fields.Str(required=False)  # noqa: N815
+
+
+class DashboardLineageDashboardSchema(Schema):
+    id = fields.Integer()
+    title = fields.String()
+    slug = fields.String()
+    published = fields.Boolean()
+
+
+class DashboardLineageChartSchema(Schema):
+    id = fields.Integer()
+    slice_name = fields.String()
+    viz_type = fields.String()
+    dataset_id = fields.Integer()
+
+
+class DashboardLineageDatasetSchema(Schema):
+    id = fields.Integer()
+    name = fields.String()
+    database_id = fields.Integer()
+    database_name = fields.String()
+    schema = fields.String(allow_none=True)
+    table_name = fields.String()

Review Comment:
   **Suggestion:** This schema marks dataset database fields as non-null, but 
the dashboard lineage API intentionally redacts them to `None` when the user 
lacks datasource access. The nullability contract is inconsistent and can break 
generated clients or strict response validation; mark these fields as nullable. 
[api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ⚠️ Dashboard lineage OpenAPI spec misstates database field nullability.
   ⚠️ Strict API clients may fail on redacted lineage payloads.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. As a user who can access a dashboard but not its underlying dataset
   (security_manager.can_access_dashboard(dash) True,
   security_manager.can_access_datasource(dataset) False per
   superset/security/manager.py:1111-1123 and 1174-1187), open the dashboard 
lineage endpoint
   GET /api/v1/dashboard/<id_or_slug>/lineage implemented at
   superset/dashboards/api.py:534-545.
   
   2. In DashboardRestApi.lineage at superset/dashboards/api.py:579-631, 
observe the
   dataset_map entry built for each chart.datasource: when 
can_access_datasource(dataset) is
   False, the code sets "database_id": dataset.database_id if can_access else 
None and
   "database_name": dataset.database.database_name if can_access and 
dataset.database else
   None (lines 599-610), intentionally redacting these fields to None while 
keeping "id" and
   "name" populated.
   
   3. The OpenAPI schema for this response is declared as 
DashboardLineageResponseSchema at
   superset/dashboards/schemas.py:689-692, which nests 
DashboardLineageDatasetSchema defined
   at lines 652-659; that schema marks database_id = fields.Integer() and 
database_name =
   fields.String() without allow_none=True, while only schema is nullable.
   
   4. Generated clients or strict response validators using the OpenAPI
   components/schemas/DashboardLineageResponseSchema (referenced at
   superset/dashboards/api.py:559-565) will treat database_id and database_name 
as
   non-nullable, and will reject or crash on lineage responses where these 
fields are set to
   null for redacted datasets, revealing the contract mismatch between
   superset/dashboards/api.py:599-610 and 
superset/dashboards/schemas.py:652-657.
   ```
   </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=2efad5c09dd84163a22cfa3e954cde8e&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=2efad5c09dd84163a22cfa3e954cde8e&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/dashboards/schemas.py
   **Line:** 655:658
   **Comment:**
        *Api Mismatch: This schema marks dataset database fields as non-null, 
but the dashboard lineage API intentionally redacts them to `None` when the 
user lacks datasource access. The nullability contract is inconsistent and can 
break generated clients or strict response validation; mark these fields as 
nullable.
   
   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%2F40912&comment_hash=b236b58d08d0d573967989e53a997946dcff4101b630f375ce9c20aa1c216e70&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40912&comment_hash=b236b58d08d0d573967989e53a997946dcff4101b630f375ce9c20aa1c216e70&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