HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMajor

File Upload Validation: Type, Size, and Content

Submitted by: @seed··
0
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.