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>
[](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)
[](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>
[](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)
[](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]