Skip to content

Commit 42f08a3

Browse files
committed
fix: wrap fs.readFile in try/catch and move tempFilePath onto File type
1 parent fe7028d commit 42f08a3

3 files changed

Lines changed: 70 additions & 5 deletions

File tree

packages/payload/src/uploads/checkFileRestrictions.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,16 @@ export const checkFileRestrictions = async ({
8787
return
8888
}
8989

90-
// When useTempFiles is enabled, file.data is an empty buffer because the content
91-
// is stored on disk at tempFilePath instead. Read the actual content so all
92-
// content-based validation (MIME detection, SVG safety, PDF integrity) still works.
90+
// file.data is empty when the content is on disk (tempFilePath). Read it so content validation works.
9391
let fileData = file.data
9492
if ((!fileData || fileData.length === 0) && file.tempFilePath) {
95-
fileData = await fs.readFile(file.tempFilePath)
93+
try {
94+
fileData = await fs.readFile(file.tempFilePath)
95+
} catch {
96+
throw new ValidationError({
97+
errors: [{ message: 'Could not read uploaded file for validation.', path: 'file' }],
98+
})
99+
}
96100
}
97101

98102
// Secondary mimetype check to assess file type from buffer

packages/payload/src/uploads/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export type UploadConfig = {
316316
}
317317
export type checkFileRestrictionsParams = {
318318
collection: CollectionConfig
319-
file: { tempFilePath?: string } & File
319+
file: File
320320
req: PayloadRequest
321321
}
322322

@@ -341,6 +341,10 @@ export type File = {
341341
* The size of the file in bytes.
342342
*/
343343
size: number
344+
/**
345+
* Path to the temp file on disk when useTempFiles is enabled. In this case file.data will be an empty buffer.
346+
*/
347+
tempFilePath?: string
344348
}
345349

346350
export type FileToSave = {

test/uploads/int.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,63 @@ describe('Collections - Uploads', () => {
11941194
}),
11951195
).rejects.toMatchObject({ name: 'ValidationError' })
11961196
})
1197+
1198+
it('should allow a valid image file when useTempFiles is enabled', async () => {
1199+
const pngData = await fs.promises.readFile(path.resolve(dirname, './image.png'))
1200+
const tmpFile = path.join(os.tmpdir(), `payload-test-${randomUUID()}.png`)
1201+
createdTmpFiles.push(tmpFile)
1202+
await fs.promises.writeFile(tmpFile, pngData)
1203+
1204+
const mockReq = {
1205+
payload: {
1206+
config: { upload: { useTempFiles: true } },
1207+
logger: { warn: () => {}, error: () => {} },
1208+
},
1209+
} as unknown as PayloadRequest
1210+
1211+
await expect(
1212+
checkFileRestrictions({
1213+
collection: {
1214+
slug: 'test',
1215+
upload: { mimeTypes: ['image/*'], staticDir: '/tmp' },
1216+
} as any,
1217+
file: {
1218+
data: Buffer.alloc(0),
1219+
mimetype: 'image/png',
1220+
name: 'valid.png',
1221+
size: pngData.length,
1222+
tempFilePath: tmpFile,
1223+
},
1224+
req: mockReq,
1225+
}),
1226+
).resolves.not.toThrow()
1227+
})
1228+
1229+
it('should throw ValidationError when tempFilePath is missing and file.data is empty', async () => {
1230+
const mockReq = {
1231+
payload: {
1232+
config: { upload: { useTempFiles: true } },
1233+
logger: { warn: () => {}, error: () => {} },
1234+
},
1235+
} as unknown as PayloadRequest
1236+
1237+
// No tempFilePath — falls through to extension-based check, which should still reject
1238+
await expect(
1239+
checkFileRestrictions({
1240+
collection: {
1241+
slug: 'test',
1242+
upload: { mimeTypes: ['image/*'], staticDir: '/tmp' },
1243+
} as any,
1244+
file: {
1245+
data: Buffer.alloc(0),
1246+
mimetype: 'text/html',
1247+
name: 'malicious.html',
1248+
size: 0,
1249+
},
1250+
req: mockReq,
1251+
}),
1252+
).rejects.toMatchObject({ name: 'ValidationError' })
1253+
})
11971254
})
11981255
})
11991256
})

0 commit comments

Comments
 (0)