Guide · Building real features
Handling file uploads
A form where strangers write files onto your infrastructure.
Uploads are where a lot of small apps get their first real security incident, because every check that stops an attack is a check that's easy to skip while you're just trying to get it working.
The rules, all mandatory
Limit the size on the server. The form attribute is a courtesy to honest users; the server limit is the protection. Without it, someone fills your disk.
Check the type by content, not by filename. A file called photo.jpg can contain anything at all. Read the actual bytes — every language has a library for this.
Never use the filename they sent. Generate your own. User-supplied names carry path traversal (../../), null bytes, and characters your filesystem will happily interpret. Store the original in the database if you need to display it; never use it on disk.
Store where it cannot execute. Object storage — S3, R2, Spaces — rather than inside your web root. If a user uploads an HTML or script file and you serve it from your own domain, they are now running code on your site for every visitor.
Serve with the correct content type and Content-Disposition, so the browser downloads rather than renders anything unexpected.
Users upload <file type>, up to <size>. Stack: <stack>. Implement it with each of these explicit, and tell me which attack each one prevents: - Server-enforced size limit. - Type checked by content, not extension. - Generated filename; original stored separately if needed. - Stored outside the web root or in object storage. - Correct content-type and disposition when serving. Then: what does the user see for too large, wrong type, and a failed upload halfway through?
Direct-to-storage uploads
For anything large, have the browser upload straight to object storage using a pre-signed URL your server generates. The file never passes through your application — saving bandwidth, memory and timeouts. Your server still decides who gets a URL, and what constraints it carries.
Practical details
Show progress for anything over a second or two. Handle the network dying mid-upload. Decide what happens to files when the record referencing them is deleted — orphaned files accumulate quietly and get expensive.
Photos from phones are enormous and carry EXIF metadata including GPS coordinates. Resize on upload, and strip metadata unless you have a specific reason to keep it.