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 * as S3Service from '../services/S3Service.js';
import {CatchAsync} from '../utils/asyncHandler.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) // Configure multer for file uploads (memory storage)
const upload = multer({ const upload = multer({
storage: multer.memoryStorage(), storage: multer.memoryStorage(),
@@ -13,12 +27,12 @@ const upload = multer({
fileSize: 10 * 1024 * 1024, // 10MB max file size fileSize: 10 * 1024 * 1024, // 10MB max file size
}, },
fileFilter: (_req, file, cb) => { 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)) { if (allowedMimeTypes.includes(file.mimetype)) {
cb(null, true); cb(null, true);
} else { } 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 // Upload file to S3/Minio
const result = await S3Service.uploadFile({ const result = await S3Service.uploadFile({
file: req.file.buffer, file: req.file.buffer,