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
- 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. - 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. - Strip EXIF Data. Images uploaded from smartphones contain GPS coordinates. Strip them using a library like
sharpbefore saving. - Serve from a separate domain. If your app is on
myapp.com, serve user uploads frommyapp-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
Related Blogs
Defending Against SSRF in Node.js Microservices
Server-Side Request Forgery is deadly. If your app fetches URLs provided by users, you are at risk. Here's how to lock down node-fetch and axios.
Beyond JWTs: Designing a Stateful, High-Performance Session Architecture
Stateless JWTs are great until you need to instantly revoke a compromised session. Here's how to build a stateful, Redis-backed authentication system that handles 50k+ concurrent users with sub-millisecond validation.
Refresh token rotation — how to detect theft in a single round-trip
Most JWT tutorials skip the hard part: what happens when a refresh token is stolen? Here's how to detect reuse, revoke session families, and do it in under 5ms.