File uploads with no type, size or ownership checks
What you'd see: Your upload handler takes file.name and file.type and saves the file.
§ 01 — What's actually happening
Both of those values come from the client and both can be set to anything. A file called <code>photo.png</code> can contain a PHP script; a file claiming <code>image/jpeg</code> can be a 4 GB zip. If the destination is publicly served and executable, that is a very short path to running code on your server.
Why an AI tool writes it this way
"Let users upload a profile picture" describes the happy path, and the happy path is what gets built. The <code>accept="image/*"</code> attribute on the input looks like validation, but it only filters the file picker dialog.
What it costs you
Ranges from storage bills and broken pages to remote code execution on hosts that run PHP from the upload directory. Even without execution, an unbounded upload endpoint is a free file host — and you pay the bandwidth.
§ 02 — Before & after
The upload box that accepts anything
Illustrative code, written for this page — never a client's project.
AI-generated
- Trusts the client-supplied MIME type and extension
- No size limit, so one request can fill the disk
- Original filename used as the path —
../included
Human-reviewed
- Type confirmed by reading the file's magic bytes
- Hard size limit enforced before anything is written
- Random filename, correct extension, stored outside the executable path
§ 03 — Check your own
How to tell in two minutes
You don't need us to run these. If any of them come back the wrong way, you have this problem.
- Rename a text file to
test.pngand upload it. If it is accepted, only the extension is being checked. - Try a large file — 100 MB or so. A server that accepts it will accept a thousand of them.
- Look at where files land. If uploads sit in a folder your web server executes, that is the serious version of this bug.
- Check the stored path includes the user ID or a random component. Predictable paths let one user overwrite another's file.
Found it in your project? Fixing this one properly usually takes an engineer under an hour. Finding the other nine takes longer — which is what the free health check is for.
Get my free health check →§ 04 — Related
Other things we find
Stripe webhook accepts unverified events
Your webhook route reads the request body and acts on it directly.
Read the fix → CRITICAL Database & dataSupabase Row Level Security is off, or has no policy
Your app works, and the Supabase dashboard shows "RLS disabled" or "No policies" next to your tables.
Read the fix → CRITICAL Exposed credentialsSecret keys shipped to the browser
You have an environment variable named something like NEXT_PUBLIC_OPENAI_API_KEY or VITE_STRIPE_SECRET_KEY.