A JavaScript file upload is only as safe as the server code that receives it. Browser-side checks improve the experience by catching mistakes early, but they cannot protect your application, because anyone can send a request directly with an intercepting proxy and skip your front-end code entirely. The security boundary is the server, and seven checks should run there every time a file arrives.
Where JavaScript fits and where it stops
Client-side code is still worth writing. It can reject an oversized file before the transfer starts, show a readable error, and prevent a user from picking an obviously wrong type. The problem is treating those checks as security. OWASP’s File Upload Cheat Sheet is explicit that client-side restrictions can be trivially bypassed with an intercepting proxy, so every rule your front end enforces must be enforced again on the server.
As an Amazon Associate I earn from qualifying purchases.
| Concern | Browser-side JavaScript | Server-side enforcement |
|---|---|---|
| Main purpose | Immediate feedback and better usability | Security policy and the final decision to store or reject a file |
| Can a user bypass it? | Yes, by sending a crafted request directly | Not through the client; the attacker can only send data the server still evaluates |
| Trust in extension or Content-Type | Only useful as a hint for the user | Treated as untrusted input and checked against the actual content |
| Where files are stored | Not applicable | Isolated, non-executable location chosen by the server |
The seven server-side checks
1. Allow only the file types the feature needs
Start from the business requirement, not from a list of dangerous types. An avatar feature needs PNG, JPEG, or WebP; an invoice import may need only PDF or CSV. Write that narrow allowlist down and enforce it on the server.
Filename handling is where many checks fail. Decode and normalise the submitted name before you read its extension, then take the final extension from the normalised value. Pay attention to:
#1 Best Overall
- Multiple extensions, such as
report.pdf.exeorphoto.jpg.php, where one component of the stack may read the first extension and another the last. - Case variants, such as
.PHPor.Jpg, which bypass case-sensitive comparisons. - Null bytes, which some language runtimes and older parsers treat as the end of a string, so
shell.php%00.jpgmay be stored asshell.php. - Parser differences between your validator, your framework, and the file system.
A blocklist of dangerous extensions will always miss something, and a simple regular expression such as one that only matches a trailing .jpg is easy to get around. Use an exact allowlist of extensions and types instead.
2. Validate the actual file type and content
The Content-Type header is supplied by the client, so treat it as an unverified claim. Determine the real type by inspecting the content with a parser that understands the permitted format. For images, that means decoding the file; for PDFs, using a PDF parser; for CSV, checking that it parses as text with the expected structure.
File signatures (magic bytes) are useful as a fast first filter, but OWASP cautions that signatures alone are bypassable. A file can begin with a valid header and still carry other content, so a signature match should never be the only test.
Rank #2
3. Replace user-controlled storage names and paths
Never build a storage path from a submitted filename. Generate a random internal name on the server, store the mapping between that name and the user-facing name in your database, and write the file only under a fixed base directory. This also prevents overwrites: two users uploading resume.pdf must never collide on disk.
import { randomUUID } from "node:crypto";
import path from "node:path";
const UPLOAD_ROOT = "/srv/uploads-private"; // outside the webroot
const ALLOWED = new Map([
["image/png", ".png"],
["image/jpeg", ".jpg"],
]);
// detectedType must come from content inspection, not the request header
function storagePathFor(detectedType) {
const ext = ALLOWED.get(detectedType);
if (!ext) throw new Error("File type not allowed");
return path.join(UPLOAD_ROOT, randomUUID() + ext);
}
If you keep a user-facing name for display, validate it separately, limit its length and characters, and encode it correctly when you serve the download (see check 7).
4. Set size, quota, and archive limits
Enforce a maximum file size on the server, and where the feature needs it, a per-user quota so that one account cannot fill your storage. OWASP’s concern includes oversized files and archive bombs, which exhaust memory, disk, or CPU during processing.
For archives, check limits before extraction, not after. Cap the total uncompressed size, the number of entries, and the nesting depth. Reject entries whose paths are absolute or contain traversal sequences such as ../, because extraction without that check can write files outside the intended directory. The OWASP ASVS 5.0 file-handling chapter describes the same expectations: documented maximum sizes including unpacked size, archive expansion and file-count limits, and per-user quotas.
Recommended Free Tools
5. Inspect content and scan where appropriate
An allowed extension does not mean the content is safe. Apply validation suited to each permitted format, and where your risk profile calls for it, run anti-malware scanning. Keep newly uploaded files in a quarantined state until the scan completes, and reject or isolate anything flagged before any other user can retrieve it.
Two cautions apply. First, image re-encoding is a useful technique: decode the image and write it back in an allowed format, which removes many embedded payloads. OWASP notes, however, that rewriting is not a guarantee, and the image processor itself handles untrusted input and must be kept patched. Second, some third-party services, including VirusTotal, offer APIs to check files against known malicious hashes. Such checks can only detect known threats, and sending file data or metadata to an outside service raises privacy and data-leakage questions, so review what leaves your system before you adopt one. Neither conversion nor antivirus should be described to users or staff as a complete guarantee.
Rank #4
6. Store uploads in an isolated, non-executable location
Store uploaded files outside the webroot, or on a separate host or object storage, so that the web server never executes them as code. Give the application only the file-system permissions it needs for the upload directory, and configure that location so that scripts placed there cannot run when requested directly. Keep in mind that isolation is an architectural layer: it limits damage, but it does not replace validation.
7. Control who uploads and who can retrieve files
Require authentication for upload endpoints, and check that the authenticated user is allowed to upload to that particular resource. Retrieval needs its own authorization check. An unguessable URL is not an access control, so the download endpoint should confirm that the requester owns the file or has permission to view it.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When serving a file, do not reuse the submitted filename as the response filename without handling it. Either ignore it and use a server-generated name, or validate it and send it with a safely encoded Content-Disposition header. OWASP also flags active content such as XSS or CSRF exposure when publicly retrievable files are served in a way that a browser interprets as executable, so make sure the response type matches the validated type.
Best Value
Why no single check is enough
These seven checks are defense in depth, not interchangeable alternatives. The OWASP File Upload Cheat Sheet states: “There is no silver bullet in validating user content.” Each check catches a different failure: a valid-looking file with the wrong content, a clever filename, an oversized archive, or a public URL that exposes another user’s document. Skipping one usually leaves a gap that the others cannot close.
The emphasis differs by feature. For profile images, checks 1, 2, 3, and 6 matter most, plus re-encoding. For document uploads that other users retrieve, checks 5 and 7 gain weight. For archive imports, check 4 is essential before any extraction runs. Build all seven into the design from the start, because retrofitting them into an existing upload path is harder than writing them correctly the first time.
Quick Recap
“
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




