From 1be0e6d6fa3ae3bdcaf0bfa382416eed414cdb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Fri, 10 Apr 2026 21:40:48 +0300 Subject: [PATCH] feat(tags): add validation to ensure tags belong to the project to prevent IDOR --- app/api/comments/[commentId]/route.ts | 13 ++++++++++++- app/api/versions/[versionId]/comments/route.ts | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/api/comments/[commentId]/route.ts b/app/api/comments/[commentId]/route.ts index 0d18f2d..3990c11 100644 --- a/app/api/comments/[commentId]/route.ts +++ b/app/api/comments/[commentId]/route.ts @@ -173,7 +173,18 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) { const updateData: Record = {}; if (content !== undefined && typeof content === 'string') updateData.content = content.trim(); - if (tagId !== undefined) updateData.tagId = tagId; + if (tagId !== undefined) { + // Verify tag belongs to this project to prevent cross-project tag leakage (IDOR) + if (tagId !== null) { + const tag = await db.commentTag.findFirst({ + where: { id: tagId, projectId: project.id }, + }); + if (!tag) { + return apiErrors.badRequest('Tag not found'); + } + } + updateData.tagId = tagId; + } if (annotationData !== undefined) { if (annotationData === null) { updateData.annotationData = null; diff --git a/app/api/versions/[versionId]/comments/route.ts b/app/api/versions/[versionId]/comments/route.ts index 2369bbc..8899ef2 100644 --- a/app/api/versions/[versionId]/comments/route.ts +++ b/app/api/versions/[versionId]/comments/route.ts @@ -293,6 +293,16 @@ export async function POST(request: NextRequest, { params }: RouteParams) { return apiErrors.badRequest('Guest name is required for guest comments'); } + // Verify tag belongs to this project to prevent cross-project tag leakage (IDOR) + if (tagId) { + const tag = await db.commentTag.findFirst({ + where: { id: tagId, projectId: project.id }, + }); + if (!tag) { + return apiErrors.badRequest('Tag not found'); + } + } + if (voiceUrl && !SAFE_AUDIO_PATH.test(voiceUrl)) { return apiErrors.badRequest('Voice URL must reference an uploaded audio file'); }