Secure File Uploads in Web Apps: A Practical Guide for Startups

Secure File Uploads in Web Apps: A Practical Guide for Startups — cover image

File upload looks like one of the easiest features to add: a button, a form, a folder on the server. It is also one of the most common places where real security incidents begin. Attackers love upload endpoints because they accept untrusted data, often write to disk and sometimes serve that data back to other users.

If your product lets users upload profile photos, invoices, resumes, videos or documents, this guide walks you through a safe, scalable design. You will learn how to use presigned URLs, validate content properly, scan for malware, keep storage private and avoid the mistakes that cause data leaks.

What can go wrong with file uploads

Before designing the solution, it helps to understand the threats:

A real-world example: a recruitment portal

Imagine an illustrative case. A hiring platform lets candidates upload resumes, and recruiters download them. The first version saves files into a folder on the web server using the original filename and serves them from a public path.

Problems appear quickly. Two candidates upload files with the same name and one overwrites the other. Resumes are reachable by anyone who guesses the URL, which exposes personal data. A recruiter opens a macro-laden document that was never scanned. And as traffic grows, large PDFs stream through the API server and slow down every other request.

The redesign moves to private object storage, random file keys, direct uploads through presigned URLs, file type validation and scanning before recruiters can download. Each change addresses one of the failures above, and together they make the feature safe and scalable.

Step-by-step secure upload design

  1. Define what you accept. List the exact file types, maximum sizes and counts per user. Fewer allowed types means a smaller attack surface.
  2. Require authentication and authorization. Only signed-in users with the right permissions should request an upload. Check ownership again on download. Our guide to designing roles and permissions for SaaS covers how to model who can see which file.
  3. Ask your API for an upload permission. The client sends the intended filename, type and size. Your server checks them against your rules and returns a presigned URL or form policy that is valid for a few minutes.
  4. Generate your own storage key. Never trust the original filename. Create a random identifier (for example a UUID) and a safe path structure, and keep the original name only as metadata for display.
  5. Upload directly to object storage. The browser or mobile app sends the file straight to the storage service using the presigned URL. Your API server never handles the bytes.
  6. Constrain the signed request. Include content length limits and the expected content type in the policy so the storage service rejects anything else.
  7. Quarantine new uploads. Write files to a staging bucket or prefix marked as unverified. They must not be downloadable or processed until checks pass.
  8. Verify content on the server side. A background worker reads the file's magic bytes to confirm the real type, enforces size limits again and rejects mismatches.
  9. Scan for malware. Run an antivirus engine or a managed scanning service on the quarantined file. Move clean files to the final private location, and delete or flag infected ones.
  10. Process safely. For images, resize and re-encode them with a well-maintained library, which also strips hidden metadata and many embedded payloads. Run conversions in a sandboxed or low-privilege worker.
  11. Serve through signed or authenticated access. Generate short-lived download URLs after checking permissions, and set headers that prevent browsers from executing content.
  12. Log and monitor. Record who uploaded what and when, and alert on unusual volumes or repeated validation failures.

Why presigned URLs help

With a presigned URL, your server issues a time-limited permission to write one specific object to storage. The client uploads directly to the storage service. This gives you several advantages:

Keep expiry short, usually minutes, and never expose long-lived storage credentials to the client. Treat those credentials like any other secret, as described in our guide to secrets management for startups.

Validation: trust nothing the client says

Check the real file type

Read the file's signature rather than relying on the extension or the Content-Type header, both of which the client controls. Maintain an allow list, not a block list.

Handle risky formats carefully

SVG files can contain scripts, and HTML files obviously can too. Either avoid accepting them, or sanitise them, or serve them from a separate domain with strict headers so they cannot touch your session cookies.

Limit size and rate

Enforce maximum size in the signed policy and again after upload. Apply per-user rate limits to the request-upload endpoint so one account cannot flood storage.

Control the filename and metadata

Strip path characters, limit length, and escape the display name wherever it appears in the interface to avoid injection.

Safe delivery of files

Privacy, retention and compliance

Uploaded files frequently contain personal or financial data, so treat them accordingly. Encrypt data at rest, restrict who can list buckets, and write a retention rule so old files are deleted when no longer needed. If your users are in India, the obligations in the DPDP Act may apply to documents you store, and a clear deletion workflow makes responding to user requests much easier.

Key benefits of doing uploads right

A practical security checklist

Use this checklist during design reviews and before every release that touches file handling. It is deliberately short so it actually gets used.

Handling large files and poor networks

Mobile users on unreliable connections are common, especially when they upload videos or scanned documents. Multipart uploads split a file into parts that are sent independently, so a dropped connection resumes from the last successful part instead of starting over. Show progress clearly, allow cancellation and clean up incomplete uploads automatically with a lifecycle rule, otherwise abandoned parts quietly accumulate storage cost.

For images and video, consider generating thumbnails and lower-resolution versions in a background job. Serving a small preview to most viewers reduces bandwidth and makes pages load faster. Keep the original in private storage, and expose it only to users who need it.

Testing your upload pipeline

Treat the upload path as a security-sensitive feature and test it with hostile input. Try files with double extensions, mismatched content types, empty files, files at exactly the size limit, filenames with unusual characters, and archives that expand to enormous sizes. Verify that rejected files leave no residue in storage, that error messages do not reveal internal paths, and that expired presigned URLs genuinely stop working. Automated tests for these cases belong in your regular pipeline, so a later refactor cannot silently weaken the protections.

Conclusion

Secure uploads are not about one clever trick. They are a chain of small, sensible controls: allow only what you need, let users upload directly with short-lived permissions, quarantine and verify every file, store privately and serve with care. Build the pipeline once and reuse it across every feature that accepts files. If you are planning a product with documents, media or user-generated content, our web development team can design the upload architecture as part of the initial build rather than patching it after an incident.

Frequently Asked Questions

Why should uploads go directly to object storage?
Direct uploads with presigned URLs keep large files off your application servers, which saves bandwidth and memory, and lets you scale uploads independently from your API.
Is checking the file extension enough?
No. Extensions and client-supplied content types are easy to fake. Validate the actual file signature (magic bytes) on the server and restrict allowed types to a short list.
Should uploaded files be publicly readable?
Usually not. Store them in a private bucket and serve them through short-lived signed URLs or an authenticated endpoint, unless the file is intentionally public, like a product image.
Do I need malware scanning for every upload?
If users can upload documents that other people will download or open, scanning is strongly advisable. For images processed and re-encoded by your own pipeline, the risk is lower but not zero.
How large should I allow uploads to be?
Set limits based on real use cases, enforce them on the server or in the signed policy, and use multipart uploads for large files so failed transfers can resume.