Step-by-Step Guide
5 steps

How to Secure File Uploads

File uploads are one of the riskiest features in web applications. Without proper validation, attackers can upload malicious scripts, oversized files, or content that exploits other users. This guide covers every aspect of secure file handling.

Find security issues automatically before attackers do.

Follow These Steps

1

Validate file types by content, not just extension

File extensions can be spoofed. Check the actual file content using magic bytes.

Code Example
// Check file magic bytes (Node.js)
import { fileTypeFromBuffer } from 'file-type'

async function validateFileType(buffer: Buffer, allowedTypes: string[]) {
  const type = await fileTypeFromBuffer(buffer)
  if (!type || !allowedTypes.includes(type.mime)) {
    throw new Error('Invalid file type')
  }
  return type
}

// Usage
const allowed = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']
await validateFileType(fileBuffer, allowed)
2

Enforce file size limits

Set maximum file sizes to prevent resource exhaustion.

Code Example
// Express.js with multer
import multer from 'multer'

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB max
    files: 1                     // 1 file per request
  }
})

// Next.js route config
export const config = {
  api: { bodyParser: { sizeLimit: '5mb' } }
}
3

Rename uploaded files

Never use the original filename. Generate a random name to prevent path traversal and overwrite attacks.

Code Example
import { randomUUID } from 'crypto'
import path from 'path'

function generateSafeFilename(originalName: string): string {
  const ext = path.extname(originalName).toLowerCase()
  const allowedExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.pdf']
  if (!allowedExtensions.includes(ext)) throw new Error('Invalid extension')
  return `${randomUUID()}${ext}`
}
4

Store files outside the web root

Use cloud storage (S3, Supabase Storage) or a directory not served by your web server.

Code Example
// Upload to Supabase Storage
const { data, error } = await supabase.storage
  .from('uploads')
  .upload(`user-files/${userId}/${safeFilename}`, fileBuffer, {
    contentType: validatedType.mime,
    upsert: false
  })

// Serve via signed URLs (time-limited access)
const { data: urlData } = await supabase.storage
  .from('uploads')
  .createSignedUrl(`user-files/${userId}/${safeFilename}`, 3600)
5

Set correct Content-Type and Content-Disposition headers when serving

Prevent browsers from executing uploaded files as code.

Code Example
// When serving uploaded files
res.setHeader('Content-Type', file.mimeType)
res.setHeader('Content-Disposition', 'attachment; filename="file.pdf"')
res.setHeader('X-Content-Type-Options', 'nosniff')

What You'll Achieve

File uploads are validated by content type, limited in size, stored with random filenames in cloud storage, and served with secure headers. Your application is protected against malicious file upload attacks.

Common Mistakes to Avoid

Mistake

Validating only the file extension

Fix

File extensions can be spoofed. Use magic byte detection (file-type library) to verify actual content.

Mistake

Storing uploads in the public web directory

Fix

Uploaded files in the web root can be executed by the server. Use cloud storage or a directory outside the web root.

Mistake

Using the original filename

Fix

Original filenames can contain path traversal sequences (../../). Always generate a random filename.

Frequently Asked Questions

Is checking the file extension enough?

No. Extensions can be changed trivially. Use magic byte detection to verify the actual file type matches the expected type.

Where should I store uploaded files?

Use cloud storage like S3, Supabase Storage, or Cloudflare R2. Never store in your web root. Serve through signed URLs with proper Content-Type headers.

Ready to Secure Your App?

VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.

Start Security Scan