Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.

File Upload Security

Handling user file uploads safely: validating type by content not extension, enforcing size limits, re-encoding images, storing outside the webroot, random filenames, never executing from upload dirs, malware scanning, and correct serving headers.

Mby @markdownersPublished August 21, 2026 · ~4 min read

0 downloads · Used by 0 stacks

A file upload endpoint accepts attacker-controlled bytes and, unless every step below is followed, a plausible-looking image or document can become a path to remote code execution, stored XSS, or unbounded storage abuse.

Validate type by content, not extension

  • Never trust the filename extension or the client-supplied Content-Type header to determine file type — both are attacker-controlled strings, not facts about the file.
  • Verify the actual file type by inspecting content (magic bytes / file signature, or a library that parses the format) and reject any mismatch between claimed type and actual content.
  • Allowlist accepted types explicitly per upload feature (e.g. image/png, image/jpeg for an avatar uploader) — never accept "any file" unless the feature is genuinely generic file storage, and even then still validate structure where the format allows it.
  • Re-validate on the server even when the client already restricts the file picker (accept="image/*") — that attribute is a UX hint, not a security control, and any client can be bypassed by calling the upload API directly.

Size limits

  • Enforce a maximum upload size server-side (and ideally at the reverse proxy/CDN layer before the request body is fully read) — an unbounded upload size is a trivial resource-exhaustion and storage-cost attack.
  • Set the limit based on the legitimate use case (an avatar needs far less than a document upload) rather than one generous limit for every endpoint.
  • Reject oversized uploads early (streaming size check, not "read the whole body then check") so a single malicious request can't exhaust memory or disk before validation runs.

Re-encode images

  • For user-uploaded images, re-encode through an image processing library (resize, re-save) rather than storing the uploaded bytes verbatim — re-encoding strips embedded scripts, malformed metadata, and format-parser exploits that a bytes-for-bytes pass-through would preserve.
  • Strip EXIF and other metadata during re-encoding by default — uploaded photos routinely carry GPS coordinates and device info the uploader didn't intend to publish.
  • Treat image-parsing libraries themselves as an attack surface: keep them updated, and never feed unvalidated uploads to a parser running with elevated privileges.

Storage location

  • Store uploaded files outside the web server's document root, or in dedicated object storage (S3-compatible bucket, etc.) — never in a directory directly served as static content by the app server, where a validation gap becomes directly exploitable.
  • Serve uploaded files through an application-controlled path (a route that checks authorization and streams the file) or signed, time-limited URLs from object storage — not a raw static file path that bypasses your access checks.
  • Never let uploaded content live under the same origin as your application's own JS/HTML unless it is served with a strict Content-Type and Content-Disposition, and ideally from a separate subdomain, to prevent a malicious upload from ever being interpreted as same-origin script.

Filenames

  • Generate a random, unpredictable filename (UUID or equivalent) for stored files server-side — never use the client-supplied filename to construct a storage path, since it can contain path traversal sequences (../), null bytes, or collide with/overwrite an existing file.
  • If the original filename must be preserved for the user (download UX), store it as metadata associated with the random storage key, and only use it to set Content-Disposition: attachment; filename="..." (properly escaped) on download — never as the actual storage path.

Never execute from upload directories

  • Configure the storage location (webroot subfolder or object storage bucket) to never execute uploaded content as code — no server-side script execution (PHP, CGI, etc.) permitted in upload directories, and object storage buckets configured for static asset serving only.
  • Set a strict Content-Type on every served upload based on your own type validation, never based on a client-supplied or filename-inferred value — an uploaded .html or .svg file served with an executable/scriptable content type is a stored-XSS vector even without server-side code execution.

Malware scanning

  • For uploads from untrusted or public-facing sources, run files through a malware/antivirus scanning step before they're made available to other users — this is a defense-in-depth layer on top of, not instead of, type and content validation.
  • Treat scanning as best-effort risk reduction, not a guarantee — combine it with the other controls here rather than relying on it alone.

Serving with correct headers

  • Set Content-Type from your own validated type determination, and Content-Disposition: attachment for any file type that a browser might otherwise render/execute inline (HTML, SVG, XML) — inline disposition should be reserved for types you've confirmed are safe to render (validated images, PDFs behind a sandboxed viewer).
  • Set X-Content-Type-Options: nosniff on upload-serving responses so browsers don't second-guess your declared Content-Type based on content sniffing.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/file-upload-security)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion