patternjavascriptMajor
File Upload Validation: Type, Size, and Content
Viewed 0 times
file uploadmime typemagic bytesfile-typemultercontent validationmalware upload
Problem
Accepting file uploads without validation allows attackers to upload server-executable files, oversized files for DoS, or files with malicious content masquerading as safe types.
Solution
Validate MIME type from file content (magic bytes), not from the client-supplied Content-Type. Enforce file size limits. Store uploads outside the web root and serve via a controller, not directly.
Why
The Content-Type header and file extension are both attacker-controlled. Inspecting the first bytes of the file (magic bytes) reveals its actual type regardless of the declared type.
Gotchas
- Multer's fileFilter only checks the MIME type declared by the client—use the file-type npm package to verify from content
- Rename all uploaded files to random UUIDs to prevent directory traversal via crafted filenames
- Never serve uploaded files from the same origin as your app—use a separate domain or a CDN to isolate execution context
- Scan uploaded files with ClamAV or a cloud malware scanner before making them available to other users
- Set Zip bomb limits—a tiny ZIP can expand to gigabytes
Code Snippets
Validating uploaded file type from magic bytes using file-type
const multer = require('multer');
const { fileTypeFromBuffer } = require('file-type');
const { v4: uuidv4 } = require('uuid');
const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'application/pdf']);
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 } // 5 MB
});
app.post('/upload', upload.single('file'), async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file provided' });
// Validate from content, not client-declared type
const detected = await fileTypeFromBuffer(req.file.buffer);
if (!detected || !ALLOWED_TYPES.has(detected.mime)) {
return res.status(400).json({ error: 'File type not allowed' });
}
const filename = `${uuidv4()}.${detected.ext}`;
await saveToStorage(req.file.buffer, filename);
res.json({ filename });
});Revisions (0)
No revisions yet.