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
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N
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
- Source · attacker-controlledpackages/
frontend/ src/ features/ apps/ hooks/ useAppSdkBridge.ts:557 useAppSdkBridge()
- 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; - 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); - 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 }, "*"); - 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
Publish attacker-controlled app source in project A.
- 2
Have a broader-authorized viewer open the shared app.
- 3
Post an allowed-looking metric-query path containing project B's UUID.
- 4
Poll the returned query ID through the same bridge.
- 5
Receive project-B result rows inside the project-A app iframe.
Proof of concept
- 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.
- 02
Target configuration
Create a harmless project-B metric query and place the malicious project-A app in a space the victim can view.
- 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. - 04
Expected response
The vulnerable parent page sends project-B rows back in a
lightdash:sdk:fetch-responsemessage to the project-A iframe. - 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.
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);
Check the upstream record for the project's current remediation status.
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.
