From 953754d6e322aaee3caf1de9bc87dacbf27f8ea2 Mon Sep 17 00:00:00 2001 From: VitalCheffe Date: Tue, 31 Mar 2026 21:19:53 +0000 Subject: [PATCH] fix(security): prevent path traversal in deleteFile (#75) The deleteFile function used path.resolve(path.normalize(file.path)) without validating that the resulting path stays within the user's uploads directory. If an attacker could control the path value stored in the database (e.g. via backup restore), they could delete arbitrary files on the server. Now uses safePathJoin (consistent with the rest of the codebase) to resolve file.path relative to the user's uploads directory, which throws if the resolved path escapes the base directory. Fixes #75 --- models/files.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/models/files.ts b/models/files.ts index 8d6dd446..d414ac8d 100644 --- a/models/files.ts +++ b/models/files.ts @@ -2,6 +2,7 @@ import { prisma } from "@/lib/db" import { unlink } from "fs/promises" +import { safePathJoin, FILE_UPLOAD_PATH } from "@/lib/files" import path from "path" import { cache } from "react" import { getTransactionById } from "./transactions" @@ -74,7 +75,12 @@ export const deleteFile = async (id: string, userId: string) => { } try { - await unlink(path.resolve(path.normalize(file.path))) + // Use safePathJoin to prevent path traversal attacks (issue #75). + // file.path is relative to the user's uploads directory. + const user = await prisma.user.findUniqueOrThrow({ where: { id: userId } }) + const userUploadsDir = safePathJoin(FILE_UPLOAD_PATH, user.email) + const fullPath = safePathJoin(userUploadsDir, file.path) + await unlink(fullPath) } catch (error) { console.error("Error deleting file:", error) }