File upload features are everywhere — avatars, attachments, document imports, bulk data loads — and they are consistently under-reviewed for a simple reason: the happy path is easy to verify by testing. Upload a normal image, confirm it displays, ship it. The failure modes only show up when someone deliberately uploads a file crafted to be something other than what it claims to be, which is exactly the scenario a quick manual test never covers.
Client-side validation is a UX nicety, not a control
An accept="image/*"attribute or a JavaScript extension check makes the browser's file picker more convenient — it does nothing to stop anyone who sends the upload request directly, bypassing the browser UI entirely. Every validation rule that matters for security has to be enforced server-side, on the bytes that actually arrive, regardless of what the client claimed about them.
Checking the extension is not checking the file
A file named invoice.jpg can contain anything — including a working PHP or JSP script, renamed specifically to defeat an extension check. Validating actual file content, not the filename, is what matters: checking magic bytes against the claimed type, or using a real content-sniffing library rather than trusting the Content-Typeheader the client sent. Even content-based checks aren't bulletproof — polyglot files can be crafted to be simultaneously valid as two different formats, which is one more reason defense in depth matters here more than a single clever check.
Where an uploaded file gets executed unintentionally
- Storing uploads inside a directory the web server will execute scripts from, so a file that slips past validation as
.phpor.jspruns as code the moment it's requested. - Serving uploaded SVG or HTML files inline with your application's own origin — both formats can carry embedded
<script>content, turning a file upload feature into a stored XSS vector. - Image-processing libraries used to resize or transform uploads, which have their own history of exploitable parsing bugs when fed a deliberately malformed file.
Practical controls worth checking for
- Uploaded files are stored outside the web root, or in object storage with no execute permission at all.
- Uploads are served from a separate domain or subdomain, isolating any script they might contain from your main application's session cookies.
- Images are re-encoded rather than having the original bytes passed straight through, which strips most embedded exploits along the way.
- A real file size limit is enforced server-side, not just suggested in the UI.
- Malware scanning is applied where the risk profile justifies it — a public-facing upload feature handling arbitrary file types is a very different risk than an internal tool with a handful of trusted users.
Test it like an attacker would, not like a user would
The most useful review question for an upload feature isn't "does a normal file work" — it's "what happens if I upload a script renamed to look like an image, or an SVG with a payload inside, or a file ten times larger than expected." If nobody on the team can answer that with confidence, the feature isn't actually ready, regardless of how clean the happy path looks.