CVE-2026-77769 Overview
CVE-2026-77769 is an authorization flaw in OpenPanel, an open-source product analytics platform. The report.list tRPC procedure in packages/trpc/src/routers/report.ts accepts both a projectId and a dashboardId but only validates membership against the supplied projectId. An authenticated user can pair their own projectId with any dashboardId from another organization and retrieve every report in that dashboard. The vulnerability is classified as an Insecure Direct Object Reference [CWE-639].
Critical Impact
Any authenticated OpenPanel user can read reports belonging to dashboards owned by other organizations, resulting in cross-tenant data disclosure.
Affected Products
- OpenPanel (self-hosted product analytics platform)
- @openpanel/trpc package containing the vulnerable report.list procedure
- Deployments running the commit tree prior to 0a51b6805eed0b3da8376175acd5fa3d26819cb6
Discovery Timeline
- 2026-08-21 - CVE-2026-77769 published to NVD
- 2026-08-26 - Last updated in NVD database
Technical Details for CVE-2026-77769
Vulnerability Analysis
The vulnerability stems from missing cross-parameter validation between two tRPC inputs. The enforceAccess middleware in packages/trpc/src/trpc.ts verifies that the caller belongs to the organization owning projectId. The middleware does not confirm that the supplied dashboardId also belongs to that project. The underlying data-layer function getReportsByDashboardId in packages/db/src/services/reports.service.ts selects reports using dashboardId alone, with no project scoping applied at the query level.
A correctly scoped helper named listReportsCore already exists in the same service file. That helper resolves the dashboard through getDashboardById(dashboardId, projectId) before returning reports. The router simply did not call it, leaving the tenant boundary unenforced.
Root Cause
The root cause is a broken access control pattern where the authorization decision uses one parameter (projectId) but the data lookup uses an unrelated parameter (dashboardId). This is a textbook Insecure Direct Object Reference [CWE-639]. The middleware trusts client-supplied identifiers as coherent, when in practice an attacker can mix identifiers from different trust domains.
Attack Vector
An authenticated user calls the report.list tRPC endpoint with their own valid projectId and a target dashboardId obtained through enumeration or prior knowledge. The middleware accepts the request because the caller has membership in the supplied project. The service function returns every report for the target dashboard, regardless of which organization owns it. No privilege escalation or authentication bypass is required beyond a standard user account.
// Patch: packages/trpc/src/routers/report.ts
import { z } from 'zod';
-import { db, getReportById, getReportsByDashboardId } from '@openpanel/db';
+import {
+ db,
+ getDashboardById,
+ getReportById,
+ getReportsByDashboardId,
+} from '@openpanel/db';
import { zReport } from '@openpanel/validation';
import { getProjectAccess } from '../access';
-import { TRPCForbiddenError } from '../errors';
+import { TRPCForbiddenError, TRPCNotFoundError } from '../errors';
import { createTRPCRouter, protectedProcedure } from '../trpc';
export const reportRouter = createTRPCRouter({
Source: OpenPanel commit 0a51b68. The patch imports getDashboardById so the router can validate that the dashboardId belongs to the supplied projectId before returning reports.
Detection Methods for CVE-2026-77769
Indicators of Compromise
- Application logs showing report.list tRPC calls where the resolved dashboardId belongs to a different organization than the caller's projectId.
- Unusual volumes of report.list requests from a single user account referencing many distinct dashboardId values.
- Database access logs on getReportsByDashboardId queries not preceded by a getDashboardById(dashboardId, projectId) lookup.
Detection Strategies
- Add server-side audit logging that records both projectId and the resolved dashboardId.projectId, and alert when they diverge.
- Review historical tRPC request logs for report.list calls and correlate dashboardId ownership against the caller's organization membership.
- Instrument the enforceAccess middleware to flag any procedure that receives multiple resource identifiers without cross-validation.
Monitoring Recommendations
- Enable request-level logging for all @openpanel/trpc procedures with authenticated user, projectId, and target resource IDs.
- Forward application and reverse-proxy logs to a centralized platform for retention and cross-tenant anomaly analytics.
- Alert on enumeration patterns where a single account issues sequential requests with incrementing or randomized dashboardId values.
How to Mitigate CVE-2026-77769
Immediate Actions Required
- Upgrade OpenPanel to a build that includes commit 0a51b6805eed0b3da8376175acd5fa3d26819cb6 from the OpenPanel repository.
- Audit application logs for prior report.list calls where a caller's projectId did not own the requested dashboardId.
- Notify affected organizations if cross-tenant access is confirmed through log review.
Patch Information
The fix is delivered in commit 0a51b68. The report router now imports getDashboardById and resolves the dashboard against the supplied projectId before returning reports. Related hardening in notification.ts adds explicit getProjectAccess checks in createOrUpdateRule. Additional context is available in the GitHub Security Advisory GHSA-3q95-vc6f-vc9v and the VulnCheck advisory.
Workarounds
- If patching is not immediately possible, replace the report.list resolver with listReportsCore, which already validates dashboard ownership through getDashboardById(dashboardId, projectId).
- Restrict network access to the OpenPanel tRPC endpoint to trusted operators until the patch is deployed.
- Rotate any dashboard identifiers that may have been enumerated and review report contents for sensitive data.
# Update OpenPanel to a patched build and restart the service
git fetch origin
git checkout 0a51b6805eed0b3da8376175acd5fa3d26819cb6
pnpm install
pnpm build
pnpm --filter @openpanel/api start
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

