Handling File Uploads Securely
Handling File Uploads Securely
File upload functionality requires special attention with MIME type security:
const multer = require('multer');
const path = require('path');
// Configure multer with MIME type validation
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
// Generate safe filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const fileFilter = (req, file, cb) => {
// Whitelist allowed MIME types
const allowedMimes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'), false);
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024 // 10MB limit
}
});
// Serve uploaded files securely
app.get('/uploads/:filename', (req, res) => {
const filename = req.params.filename;
const filepath = path.join(__dirname, 'uploads', filename);
// Validate file exists and is safe
if (!fs.existsSync(filepath)) {
return res.status(404).send('File not found');
}
// Get file extension and determine MIME type
const ext = path.extname(filename).toLowerCase();
const mimeTypes = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.pdf': 'application/pdf',
'.txt': 'text/plain'
};
const contentType = mimeTypes[ext] || 'application/octet-stream';
// Set security headers
res.setHeader('Content-Type', contentType);
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Content-Disposition', 'attachment; filename=' + filename);
// Stream file
const fileStream = fs.createReadStream(filepath);
fileStream.pipe(res);
});