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-Typeheader 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/jpegfor 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-TypeandContent-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-Typeon every served upload based on your own type validation, never based on a client-supplied or filename-inferred value — an uploaded.htmlor.svgfile 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-Typefrom your own validated type determination, andContent-Disposition: attachmentfor any file type that a browser might otherwise render/execute inline (HTML, SVG, XML) —inlinedisposition should be reserved for types you've confirmed are safe to render (validated images, PDFs behind a sandboxed viewer). - Set
X-Content-Type-Options: nosniffon upload-serving responses so browsers don't second-guess your declaredContent-Typebased on content sniffing.
Badge
Link back to this module from your own README.
[](https://markdowners.com/m/markdowners/file-upload-security)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.