Skip to content
winfunc
Disclosure record · Lightdash

Data-app SDK bridge can disclose warehouse data from other projects

The parent-page fetch bridge allowlisted route shape but not the app's trusted project

LightdashDisclosedSource record

CVSS 3.1 base score

High
7.4/ 10

Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N

Record

Project
Lightdash
Severity
High7.4
CVE
None listed
Disclosed
Trace
3 steps

Summary

Source

useAppSdkBridge()

packages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:557

Sink

useAppSdkBridge()

packages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:687

A project-A data-app author could make their sandboxed app request project-B metric queries through a victim viewer's authenticated parent page. If the viewer could access both projects, the bridge returned project-B warehouse rows to attacker-authored iframe code.

Root cause. useAppSdkBridge() accepted lightdash:sdk:fetch messages and matched generic /api/v2/projects/[^/]+/... route patterns, but never compared the path's project segment with the trusted projectUuid of the displayed app.

Remediation evidence. Lightdash merged exact PR #26511 on 30 July 2026 after the disclosure. The patch parses the bridge path, binds each project-scoped route to the current app project, rejects mismatches, and adds cross-project regression tests.

Severity

Attack vectorAV
Network
Attack complexityAC
Low
Privileges requiredPR
Low
User interactionUI
Required
ScopeS
Changed
ConfidentialityC
High
IntegrityI
None
AvailabilityA
None

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlledpackages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:557

    useAppSdkBridge()

  2. Step 01packages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:557

    A sandboxed data app posts an SDK fetch message containing an attacker-selected method, path, and analytics body.

    typescript
    const { id, method, path, body, metadata } = event.data;
    
  3. Step 02packages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:isAllowedRoute()

    The vulnerable allowlist matched any non-slash project UUID rather than the trusted project of the loaded app.

    typescript
    const projectRoute = /^\/api\/v2\/projects\/[^/]+\/query\//;
    return projectRoute.test(path);
    
  4. Step 03packages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:687

    The parent executes the project-B request with the victim's session and posts the response back to the untrusted iframe.

    typescript
    const response = await fetch(resolveFetchUrl(path), options);
    iframe.contentWindow?.postMessage({ id, response }, "*");
    
  5. Sinkpackages/frontend/src/features/apps/hooks/useAppSdkBridge.ts:687

    useAppSdkBridge()

Impact

Reported impact

Attacker-authored code can read warehouse query results from an unrelated project using the viewer as a confused deputy. The validated path is a confidentiality breach; it does not expose raw warehouse credentials or establish write access.

Attack surface

Lightdash data-app previews and embedded apps viewed by users whose project access is broader than the app author's access.

Preconditions

The attacker can author a data app in project A and knows project-B query identifiers. A victim with access to both the app and project B must open it; the attacker does not need the victim's cookie or project-B membership.

Attack path

  1. 1

    Publish attacker-controlled app source in project A.

  2. 2

    Have a broader-authorized viewer open the shared app.

  3. 3

    Post an allowed-looking metric-query path containing project B's UUID.

  4. 4

    Poll the returned query ID through the same bridge.

  5. 5

    Receive project-B result rows inside the project-A app iframe.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Run a vulnerable Lightdash deployment with data apps enabled, two projects, an attacker editor in project A, and a victim who can query both projects.

  2. 02

    Target configuration

    Create a harmless project-B metric query and place the malicious project-A app in a space the victim can view.

  3. 03

    Exploit delivery

    From the app iframe, post an SDK fetch for /api/v2/projects/<project-B>/query/metric-query, then poll the returned query through the bridge.

  4. 04

    Expected response

    The vulnerable parent page sends project-B rows back in a lightdash:sdk:fetch-response message to the project-A iframe.

  5. 05

    Outcome

    PR #26511 rejects the mismatched project segment before fetch(), while same-project SDK queries continue to work.

Remediation

Guidance

Upgrade to a Lightdash release containing PR #26511. Parse every project-scoped bridge route and require its project identifier to equal the trusted app projectUuid before making an authenticated request.

Before and after−2+5
if (!isAllowedRoute(method, path)) return blocked();return fetch(resolveFetchUrl(path), options);const routeProject = getProjectSegment(path);if (!isAllowedRoute(method, path) || routeProject !== projectUuid) {  return blocked();}return fetch(resolveFetchUrl(path), options);
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.