/ blog/secure-file-uploads-node
blog / secure-file-uploads-node / overview.md

Secure File Uploads: Why Trusting the Extension is Dangerous

Validating file uploads by checking if the filename ends in '.jpg' is a massive security hole. Learn how to securely process user uploads in Node.js.

The Extension Bypass

// DANGEROUS CODE
if (file.name.endsWith('.jpg') || file.name.endsWith('.png')) {
  await uploadToS3(file);
}

If you rely on this, an attacker can upload a file named malware.php.jpg. If your web server (like Apache or Nginx) is misconfigured, it might execute the file as PHP.

Or, they might upload an HTML file renamed to image.jpg. If you serve it directly from your domain, a browser might sniff the content, realize it's HTML, and execute embedded XSS scripts in the context of your domain.

Secure Upload Checklist

  1. Never trust the extension. Read the first few bytes (the "magic numbers") of the file to verify its true mime type using a library like file-type.
  2. Never trust the filename. Always generate a random UUID for the stored file. E.g., 550e8400-e29b-41d4-a716-446655440000.jpg. Store the original filename in your database if you need it.
  3. Strip EXIF Data. Images uploaded from smartphones contain GPS coordinates. Strip them using a library like sharp before saving.
  4. Serve from a separate domain. If your app is on myapp.com, serve user uploads from myapp-user-content.com. This prevents uploaded HTML/SVG files from executing XSS attacks against your main application domain.
import { fileTypeFromBuffer } from 'file-type';
import sharp from 'sharp';
import { v4 as uuidv4 } from 'uuid';

async function processUpload(buffer: Buffer) {
  // 1. Verify Magic Numbers
  const type = await fileTypeFromBuffer(buffer);
  if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) {
    throw new Error('Invalid file type');
  }

  // 2. Strip EXIF and re-encode
  const cleanBuffer = await sharp(buffer)
    .jpeg({ quality: 80 }) // Strips metadata by default
    .toBuffer();

  // 3. Safe Filename
  const safeFilename = `${uuidv4()}.${type.ext}`;

  await uploadToS3(cleanBuffer, safeFilename);
}

Tags

securitynodejs
0
0