Lightdash: The Lowest Project Role Runs Arbitrary SQL Against the Warehouse

The metric query takes SQL from the request, although raw SQL everywhere else is bound to abilities this role does not hold.

Advisory ID: TP-2026-093
Product: Lightdash (business intelligence interface for dbt, TypeScript)
Vulnerability type: SQL injection through a missing permission check (CWE-89)
CVE: requested (GitHub CNA), assignment pending
CVSS 3.1: 8.8 (High) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Affected versions: < 1.99.0
Vendor advisory: GHSA-cvfj-75gm-6vfw
Fixed in: 1.99.0
Reported: 13 June 2026

Summary

Lightdash provides curated explores so business users can look at numbers without writing SQL themselves. Raw SQL is bound to the abilities manage:SqlRunner and manage:CustomSql, which the role model grants to developers and administrators only. The metric query endpoints, however, take the fields customDimensions[].sql and additionalMetrics[].sql straight from the request into the query sent to the data warehouse, and the only check on that path is a view right on the explore. The interactive_viewer role, the lowest interactive project role, therefore runs arbitrary SQL against the warehouse connection. On the instance we examined, that connection ran as a database superuser.

Root cause

The shared compiler reached from runAsyncMetricQueryWithoutPermissionCheck embeds the request-supplied customDimensions[].sql and additionalMetrics[].sql strings into the warehouse query with no ability check on their SQL content (packages/backend/src/services/AsyncQueryService/AsyncQueryService.ts:4026). Both the current endpoint POST /api/v2/projects/{projectUuid}/query/metric-query and the deprecated but still mounted POST /api/v1/projects/{projectUuid}/explores/{exploreId}/runQuery forward the fields into that compiler. The only authorization on the path is a view right on the Explore subject, not the abilities raw SQL otherwise requires. A dedicated error class for exactly this case exists in the source but is never invoked, which points to a removed guard. Users receive the interactive_viewer and editor roles through an ordinary invite or the automatic join by email domain.

Proof of Concept

# as an account holding the interactive_viewer project role:
POST /api/v2/projects/<projectUuid>/query/metric-query
{
  "customDimensions": [
    {"id": "x", "name": "x", "table": "t",
     "sql": "(SELECT current_user)"}
  ],
  ...
}

# the string is embedded into the warehouse query unchanged and
# executed as SQL; the abilities manage:SqlRunner and
# manage:CustomSql are never checked on this path.

The same content is rejected as soon as it is submitted through the SQL runner, where the permission check does apply. Not affected are accounts with no project access and the plain viewer role, which cannot run ad-hoc queries.

Impact

  • Execution of arbitrary SQL against the project's warehouse connection by the lowest interactive project role.
  • Disclosure of every table that connection can see, bypassing the curated explores that the role is meant to be limited to.
  • Write access and deletion of data as far as the warehouse connection permits, which on the instance we examined meant database superuser.
  • Two routes to the same flaw, since the deprecated v1 endpoint is still mounted.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.