Stop 16 of 42 · The Teller Window · Unsafe file uploads

The deposit slot that takes any parcel

The bank has a deposit slot for parcels, and it accepts anything. Mallory posts a box labelled “photo.jpg” that is actually a power tool, then another whose name tries to slip it into the vault corridor. The slot never looks inside or checks the label.

Severity High–CriticalCategory Injection RiskThe Teller WindowSources checked 25 Oct 2026

Watch it happen

Play it as

Mallory posts a disguised tool through the deposit slot. The bank stores it on the public shelf, where it gets used, and a trick filename puts it in the wrong place.

Arrival

The bank has a deposit slot for parcels, like profile pictures or documents. Alice walks up to post one. Mallory follows her in.

1 / 5

In bank terms and app terms

In the bankIn your app
Deposit slotYour file-upload endpoint
Label on the parcelFilename and Content-Type sent by the user, which are easy to fake
Guard checking insideChecking the real file type, size limits and an allowlist of extensions
Public shelf vs locked roomWeb root folder vs private storage (S3 or Supabase bucket with safe headers)

Are you exposed?

You might be exposed if…
  • Your upload route saves files using the filename the browser sent.
  • Uploaded files are stored in a folder your web server serves directly, or accept HTML and SVG files.
  • There is no maximum size and no list of allowed file types.
What to do about it
  1. Allow only the types you need, check the real content type, and set a maximum size.
  2. Generate your own random filename and never use the user’s name or path.
  3. Store files outside the web root (private bucket), serve them with a safe content type, and never serve user HTML or SVG from your own domain.

Track these in your to-do list →

It has happened to real companies

Real-world breaches

ImageTragick (2016) — CVE-2016-3714 in ImageMagick allowed remote code execution via crafted image files. Any service that accepted user-uploaded images and processed them with ImageMagick was vulnerable.

What it looks like in code

// GOOD: Allowlist extensions, cap the size and check the real bytes (file.type comes from the client)
const ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp"]

export async function POST(request: Request) {
  const formData = await request.formData()
  const file = formData.get("file") as File
  const ext = path.extname(file.name).toLowerCase()

  if (!ALLOWED_EXTENSIONS.includes(ext)) {
    return NextResponse.json({ error: "File type not allowed" }, { status: 400 })
  }

  if (file.size > 5 * 1024 * 1024) {
    return NextResponse.json({ error: "File too large" }, { status: 413 })
  }

  // Check the file's magic bytes, not its claimed content type
  const bytes = new Uint8Array(await file.arrayBuffer())
  const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47
  const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8
  const isWebp = bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50 // "WEBP" after "RIFF"
  if (!(isPng || isJpeg || isWebp)) {
    return NextResponse.json({ error: "Invalid file content" }, { status: 400 })
  }

  // Generate a random filename (prevents path traversal)
  const safeFilename = `${crypto.randomUUID()}${ext}`
  // ... save with safeFilename, outside the web root
}

Ask your AI tool to fix it

Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.

Prompt
Harden all file upload endpoints. Allowlist extensions and MIME types and verify the real file signature, enforce a maximum size, ignore the client-supplied filename and generate a random one, store files in private storage outside the web root, serve them with Content-Disposition and X-Content-Type-Options: nosniff, and reject HTML and SVG unless sanitized. List the changes.

Where this comes from

The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.

  • File Upload Cheat SheetOWASPAllowlist extensions, check file signature, size limits, random filenames, store outside webroot
  • File upload vulnerabilitiesPortSwigger Web Security AcademyClient-supplied Content-Type is easy to fake; uploaded HTML or SVG can run script (stored XSS)
  • X-Content-Type-OptionsMDN Web Docsnosniff stops browsers guessing a content type for served files
  • Content-DispositionMDN Web DocsContent-Disposition: attachment makes the browser download the file instead of displaying it
  • ImageTragick (ImageMagick vulnerabilities)ImageTragickCVE-2016-3714: remote code execution when processing user-submitted images with ImageMagick
  • CVE-2016-3714Ubuntu SecurityAffected ImageMagick versions (before 6.9.3-10 and 7.0.1-1) allow code execution via crafted image

Spot a mistake or something out of date? Report a correction · How these guides are made and checked

Go deeper in isitsecure

The free, open-source scanner checks for most of the problems in these guides.