Server-side safety

Handle file uploads safely

Limit upload size, verify content, generate server-side filenames, isolate storage, and avoid serving untrusted files as executable content.

An upload hands your server three things the visitor controls: the bytes, the filename, and the declared media type. All three can lie, so we stack several checks.

Limit the request early

Set a maximum body size before the server buffers anything, and a per-file limit too. A profile photo endpoint that accepts 5 MB per file rejects a 200 MB body before reading it.

Be careful with archives. A 20 MB zip can expand to gigabytes. Unless the product needs them and you can inspect them safely, don’t accept them.

Allow only what you need

Start from a short allowlist. If the feature is profile photos, accept JPEG and PNG. That’s easier to defend than “any file”.

Don’t trust anything the client said about the type. file.type in JavaScript, the Content-Type of the multipart part, and the filename extension are all supplied by the client. Rename payload.exe to photo.png and all three say PNG.

Instead, look at the first bytes. A real PNG starts with 89 50 4E 47. A JPEG starts with FF D8 FF. Then hand the file to a maintained image library and let it decode. Re-encoding to a fresh file is the cleanest option: whatever was hiding in the original doesn’t survive.

Control the storage

Never use the original filename as a path. Generate a random name like 7f3a9c2e.jpg, and keep the original only as metadata. A filename of ../../etc/passwd is only dangerous if you let it touch the filesystem.

Store uploads outside the directories your application serves as code. Better still, put them on a separate origin or behind a download handler that maps a record ID to the stored object.

Serve it correctly

Set the response Content-Type yourself, from the type you verified. For anything that shouldn’t render, add Content-Disposition: attachment so the browser downloads it.

HTML and SVG deserve extra suspicion. Both can contain scripts, and an SVG served from your origin can run JavaScript with your users’ cookies. Sanitize them or serve them from a separate origin.

Permissions and lifecycle

Check that the current user may upload, and later that they may download this specific file. Scan files when the risk warrants it. Decide how long uploads live and how much each account may store.

Run the parsing step in a restricted environment. Image libraries have had serious bugs. A crash in a decoder shouldn’t expose your secrets.

Try this: upload an oversized file, a renamed executable, an image with a filename full of ../, two files with the same name, then request one as a user who shouldn’t see it. The file picker prevents none of these. Your server has to.

Lesson completed