chore: Enhance image upload validation with magic byte checks

This commit is contained in:
Dries Augustyns
2026-03-10 20:10:13 +01:00
parent 1a1e3204ee
commit 1dc423a1c4
+22 -2
View File
@@ -6,6 +6,20 @@ import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
import * as S3Service from '../services/S3Service.js';
import {CatchAsync} from '../utils/asyncHandler.js';
const MAGIC_BYTES: Record<string, Buffer[]> = {
'image/jpeg': [Buffer.from([0xff, 0xd8, 0xff])],
'image/jpg': [Buffer.from([0xff, 0xd8, 0xff])],
'image/png': [Buffer.from([0x89, 0x50, 0x4e, 0x47])],
'image/gif': [Buffer.from('GIF87a'), Buffer.from('GIF89a')],
'image/webp': [Buffer.from('RIFF')],
};
function validateMagicBytes(buffer: Buffer, mimetype: string): boolean {
const signatures = MAGIC_BYTES[mimetype];
if (!signatures) return false;
return signatures.some(sig => buffer.subarray(0, sig.length).equals(sig));
}
// Configure multer for file uploads (memory storage)
const upload = multer({
storage: multer.memoryStorage(),
@@ -13,12 +27,12 @@ const upload = multer({
fileSize: 10 * 1024 * 1024, // 10MB max file size
},
fileFilter: (_req, file, cb) => {
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (allowedMimeTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed (JPEG, PNG, GIF, WebP, SVG)'));
cb(new Error('Only image files are allowed (JPEG, PNG, GIF, WebP)'));
}
},
});
@@ -48,6 +62,12 @@ export class Uploads {
});
}
if (!validateMagicBytes(req.file.buffer, req.file.mimetype)) {
return res.status(400).json({
error: 'File contents do not match the declared image type',
});
}
// Upload file to S3/Minio
const result = await S3Service.uploadFile({
file: req.file.buffer,