Skip to content

Security: Multiple Cross-Workspace IDOR Vulnerabilities in tRPC Endpoints #250

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

Several tRPC endpoints using workspaceProcedure middleware extract workspaceId from the middleware context but do not use it in their Prisma database queries, allowing users from one workspace to access or modify resources belonging to other workspaces.

Vulnerable Endpoints

1. survey.allResultCount (survey.ts ~line 110-133)

  • Queries surveyResult.groupBy by surveyId globally without any workspaceId filter
  • The handler function async () => {} doesn't even extract input parameters
  • Impact: Returns survey result counts for ALL workspaces

2. ai.classifySurvey (ai.ts ~line 114-145)

  • Updates survey record using where: { id: surveyId } only
  • workspaceId is extracted from input but NOT used in the Prisma update() WHERE clause
  • Impact: Can modify recentSuggestionCategory of surveys in other workspaces

3. application.storeInfoHistory (application.ts ~line 255-308)

  • Queries applicationStoreInfoHistory.findMany by applicationId without workspaceId
  • Contrast: Adjacent info endpoint correctly uses where: { id: applicationId, workspaceId }
  • Impact: Reveals download counts, ratings, version history of apps in other workspaces

4. application.eventStats (application.ts ~line 309-355)

  • Calls getApplicationEventStats(applicationId) without workspace verification
  • Impact: Reveals event statistics for applications in other workspaces

5. application.sessionStats (application.ts ~line 356-404)

  • Queries applicationSession.groupBy by applicationId without workspaceId
  • Impact: Reveals session distribution (OS, version, country, language) for other workspaces

6. insights.eventNames (insights/index.ts ~line 44-77)

  • Queries websiteEvent.groupBy by websiteId without workspaceId
  • Impact: Reveals event names and page view data for websites in other workspaces

Root Cause

The workspaceProcedure middleware correctly verifies workspace membership and provides workspaceId in the input. However, these specific endpoints don't include workspaceId in their Prisma WHERE clauses when querying associated resources.

Correct Pattern (used by most endpoints)

// ✓ application.info correctly uses workspaceId
const app = await prisma.application.findUnique({
  where: { id: applicationId, workspaceId }  // Both conditions
});

Broken Pattern

// ✗ application.storeInfoHistory missing workspaceId
const history = await prisma.applicationStoreInfoHistory.findMany({
  where: { applicationId }  // Missing workspaceId!
});

Suggested Fix

Add workspaceId to all affected Prisma queries. For example:

survey.allResultCount:

.query(async ({ input }) => {
  const { workspaceId } = input;
  const res = await prisma.surveyResult.groupBy({
    by: ['surveyId'],
    where: { survey: { workspaceId } },  // ADD workspace filter
    _count: true,
  });

ai.classifySurvey:

await prisma.survey.update({
  where: { id: surveyId, workspaceId },  // ADD workspaceId
  data: { recentSuggestionCategory: [...] },
});

application endpoints:

// Verify application belongs to workspace before querying stats
const app = await prisma.application.findUnique({
  where: { id: applicationId, workspaceId }
});
if (!app) throw new Error('Application not found');

Severity

  • High for ai.classifySurvey (cross-workspace data modification)
  • Medium for the read-only information disclosure endpoints

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions