Security: Multiple Cross-Workspace IDOR Vulnerabilities in tRPC Endpoints
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.1k
- Forks
- 194
- Avg merge
- 18m
- Merged PRs (30d)
- 3
Description
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.groupBybysurveyIdglobally without anyworkspaceIdfilter - 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
surveyrecord usingwhere: { id: surveyId }only workspaceIdis extracted from input but NOT used in the Prismaupdate()WHERE clause- Impact: Can modify
recentSuggestionCategoryof surveys in other workspaces
3. application.storeInfoHistory (application.ts ~line 255-308)
- Queries
applicationStoreInfoHistory.findManybyapplicationIdwithoutworkspaceId - Contrast: Adjacent
infoendpoint correctly useswhere: { 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.groupBybyapplicationIdwithoutworkspaceId - Impact: Reveals session distribution (OS, version, country, language) for other workspaces
6. insights.eventNames (insights/index.ts ~line 44-77)
- Queries
websiteEvent.groupBybywebsiteIdwithoutworkspaceId - 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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the affected handlers in survey.ts, ai.ts, application.ts, and insights/index.ts, comparing their Prisma queries with the workspace-scoped application.info pattern. Verify that each endpoint scopes reads and updates to the workspace, then confirm that resources from another workspace cannot be accessed or modified across all six endpoints.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100