amruthpillai/reactive-resume · error · ORPCError
FORBIDDEN
FORBIDDEN
Error message
FORBIDDEN
What it means
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.
Source
Thrown at packages/api/src/features/storage/router.ts:98
.errors({
NOT_FOUND: {
message: "The specified file was not found in storage.",
status: 404,
},
FORBIDDEN: {
message: "You do not have permission to delete this file.",
status: 403,
},
})
.handler(async ({ context, input }): Promise<void> => {
const requestedKey = normalizeKey(input.filename);
const key = requestedKey.startsWith("uploads/")
? requestedKey
: normalizeKey(`uploads/${context.user.id}/pictures/${requestedKey}`);
const userPrefix = `uploads/${context.user.id}/`;
if (isUnsafeStorageKey(key) || !key.startsWith(userPrefix)) {
throw new ORPCError("FORBIDDEN");
}
const deleted = await storageService.delete(key);
if (!deleted) throw new ORPCError("NOT_FOUND");
}),
};
View on GitHub (pinned to 3a5b12e2a4)
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.
Example fix
// before: passing a path from another user
await orpc.storage.deleteFile.mutate({ filename: 'uploads/other-user/pictures/x.jpg' }); // 403
// after: delete only your own previously-uploaded path
await orpc.storage.deleteFile.mutate({ filename: myUploadedFile.path }); Defensive patterns
Strategy: validation
Validate before calling
function canDelete(filename: string, userId: string): boolean {
const norm = filename.trim().replace(/^\/+/, '').split('/').filter(Boolean).join('/');
if (norm.split('/').some(s => s === '.' || s === '..')) return false;
const own = `uploads/${userId}/`;
return norm.startsWith(own);
} Type guard
function isOwnUploadKey(filename: string, userId: string): filename is string {
return canDelete(filename, userId);
} Try / catch
try { await orpc.storage.deleteFile.mutate({ filename }); }
catch (e) { if (isORPCError(e, 'FORBIDDEN')) throw new UserError('You can only delete your own uploads.'); throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- NOT_FOUND
- PRECONDITION_FAILED
- BAD_REQUEST
- CONFLICT
- Private storage writes are not supported by the local filesy
AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12).
Data as JSON: /api/errors/c20060fa2b53375f.
Report an issue: GitHub.