{"record":{"id":"c20060fa2b53375f","repo":"amruthpillai/reactive-resume","slug":"forbidden","errorCode":"FORBIDDEN","errorMessage":"FORBIDDEN","messagePattern":"FORBIDDEN","errorType":"error_code","errorClass":"ORPCError","httpStatus":403,"severity":"error","filePath":"packages/api/src/features/storage/router.ts","lineNumber":98,"sourceCode":"\t\t.errors({\n\t\t\tNOT_FOUND: {\n\t\t\t\tmessage: \"The specified file was not found in storage.\",\n\t\t\t\tstatus: 404,\n\t\t\t},\n\t\t\tFORBIDDEN: {\n\t\t\t\tmessage: \"You do not have permission to delete this file.\",\n\t\t\t\tstatus: 403,\n\t\t\t},\n\t\t})\n\t\t.handler(async ({ context, input }): Promise<void> => {\n\t\t\tconst requestedKey = normalizeKey(input.filename);\n\t\t\tconst key = requestedKey.startsWith(\"uploads/\")\n\t\t\t\t? requestedKey\n\t\t\t\t: normalizeKey(`uploads/${context.user.id}/pictures/${requestedKey}`);\n\t\t\tconst userPrefix = `uploads/${context.user.id}/`;\n\n\t\t\tif (isUnsafeStorageKey(key) || !key.startsWith(userPrefix)) {\n\t\t\t\tthrow new ORPCError(\"FORBIDDEN\");\n\t\t\t}\n\n\t\t\tconst deleted = await storageService.delete(key);\n\n\t\t\tif (!deleted) throw new ORPCError(\"NOT_FOUND\");\n\t\t}),\n};\n","sourceCodeStart":80,"sourceCodeEnd":106,"githubUrl":"https://github.com/amruthpillai/reactive-resume/blob/3a5b12e2a40374a9571988701fcb75c5a1831c42/packages/api/src/features/storage/router.ts#L80-L106","documentation":"Authorization guard in the storage delete route. After normalizing the requested key and prefixing it with the caller's uploads directory, the handler rejects the request if the key contains path-traversal segments (. or ..) or does not start with uploads/{userId}/. oRPC raises FORBIDDEN (HTTP 403). It is a defense-in-depth ownership check, not a 'file missing' signal.","triggerScenarios":"Calling storage.deleteFile with a filename that resolves under another user's uploads/ prefix; passing a fully-qualified uploads/<otherUserId>/... key; including literal '.' or '..' segments in the filename; or supplying a key like 'uploads/' with no user-scoped suffix.","commonSituations":"Client sends a raw path from a different user's profile picture URL; an MCP/automation client constructs the key manually instead of using the path returned by uploadFile; attempts to delete shared/system files. Legitimately hit only when the caller oversteps its own uploads namespace.","solutions":["Only delete files whose path was returned by your own uploadFile call (it is already scoped to uploads/{yourUserId}/pictures/...).","If you must pass uploads/-prefixed keys, ensure the second segment is your own user id.","Never let user input build the filename unsanitized; strip leading slashes and reject any '/'-traversal on the client.","Confirm context.user.id is populated (authenticated session) — an unauthenticated call would never match the prefix."],"exampleFix":"// before: passing a path from another user\nawait orpc.storage.deleteFile.mutate({ filename: 'uploads/other-user/pictures/x.jpg' }); // 403\n// after: delete only your own previously-uploaded path\nawait orpc.storage.deleteFile.mutate({ filename: myUploadedFile.path });","handlingStrategy":"validation","validationCode":"function canDelete(filename: string, userId: string): boolean {\n  const norm = filename.trim().replace(/^\\/+/, '').split('/').filter(Boolean).join('/');\n  if (norm.split('/').some(s => s === '.' || s === '..')) return false;\n  const own = `uploads/${userId}/`;\n  return norm.startsWith(own);\n}","typeGuard":"function isOwnUploadKey(filename: string, userId: string): filename is string {\n  return canDelete(filename, userId);\n}","tryCatchPattern":"try { await orpc.storage.deleteFile.mutate({ filename }); }\ncatch (e) { if (isORPCError(e, 'FORBIDDEN')) throw new UserError('You can only delete your own uploads.'); throw e; }","preventionTips":["Only delete paths returned by your own uploadFile call.","Never build the key from untrusted user input without the ownership check.","Keep the authenticated userId in scope when issuing deletes."],"tags":["storage","authorization","security","path-traversal","orpc"],"backgroundTag":null,"analyzedSha":"3a5b12e2a40374a9571988701fcb75c5a1831c42","analyzedAt":"2026-08-12T22:31:22.666Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}