JAVASCRIPT

Robust Server-Side File Upload Validation in Node.js

Implement secure server-side validation for file uploads in Node.js, checking MIME type, file size, and performing basic content inspection to prevent malicious uploads and maintain system integrity.

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const fileType = require('file-type'); // npm install file-type

const app = express();

// Configure Multer storage and file filter
const storage = multer.memoryStorage(); // Store file in memory for validation

const upload = multer({
  storage: storage,
  limits: { fileSize: 2 * 1024 * 1024 }, // 2MB limit
  fileFilter: async (req, file, cb) => {
    const allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
    const allowedExtensions = ['.jpg', '.jpeg', '.png', '.pdf'];

    // 1. Check file size first (already handled by limits, but good to re-iterate)
    if (file.size > 2 * 1024 * 1024) {
      return cb(new Error('File too large (max 2MB)'));
    }

    // 2. Check filename extension (basic check)
    const ext = path.extname(file.originalname).toLowerCase();
    if (!allowedExtensions.includes(ext)) {
      return cb(new Error('Invalid file extension'));
    }

    // 3. Deeper MIME type check by magic numbers (more reliable than client-provided type)
    try {
      const type = await fileType.fromBuffer(file.buffer);
      if (!type || !allowedMimeTypes.includes(type.mime)) {
        return cb(new Error('Invalid file type detected'));
      }
    } catch (error) {
      return cb(new Error('Could not determine file type'));
    }

    // 4. Basic content inspection (example: for PDFs, check first few bytes for %PDF)
    if (type.mime === 'application/pdf' && !file.buffer.toString('utf8', 0, 5).startsWith('%PDF')) {
        return cb(new Error('File content does not match PDF signature'));
    }

    cb(null, true); // Accept the file
  }
}).single('myFile');

app.post('/upload', (req, res) => {
  upload(req, res, (err) => {
    if (err) {
      return res.status(400).send({ message: err.message });
    }
    if (!req.file) {
      return res.status(400).send({ message: 'No file uploaded.' });
    }
    // If validation passes, save the file to disk or process it
    const filePath = path.join(__dirname, 'uploads', req.file.originalname);
    fs.writeFileSync(filePath, req.file.buffer);
    res.status(200).send({ message: 'File uploaded and validated successfully!', filename: req.file.originalname });
  });
});

// Create uploads directory if it doesn't exist
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir);
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
How it works: This Node.js snippet demonstrates robust server-side file upload validation using `multer` for handling multipart form data and `file-type` for deeper inspection. It checks for allowed file extensions and, more critically, uses 'magic numbers' to determine the true MIME type from the file's content, which is more reliable than trusting the client-provided `Content-Type` header. It also enforces file size limits and includes a basic content signature check for PDFs, helping to prevent malicious files from being uploaded or masquerading as safe file types.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs