Letting Users Upload Files to a Web App Safely
If your web app lets users upload profile photos, CVs, invoices or project files, it accepts files from strangers and stores them on your systems, so it needs more care than a normal form field. This guide covers what can go wrong, how to check type and size, where to store files, how direct-to-storage uploads with signed URLs work, and malware scanning. It draws on the OWASP File Upload Cheat Sheet and AWS, Google Cloud and Microsoft documentation, checked on 4 November 2026.
What can go wrong
The OWASP File Upload Cheat Sheet, from the Open Worldwide Application Security Project, lists what an attacker can do with an upload feature, including:
- exploit weaknesses in the code that opens or processes the file;
- use the file for phishing, for example through a careers form;
- send ZIP bombs or very large files that fill your storage;
- overwrite an existing file on the server;
- plant active content that attacks other users if files can be viewed publicly.
If uploaded files are publicly retrievable, OWASP adds more: other files can be exposed, repeated downloads can overload the server, and you can end up hosting illegal or copyrighted content. Its central point is that "there is no silver bullet". No single check is enough, so use several layers.
Who may upload, and what
Two of OWASP's principles are about people and purpose. Only authenticated, identifiable users should be able to upload, so you can set limits and trace abuse. They should also only reach files they are authorised to see or change. Our guide to web app user authentication covers the log-in side. Authorisation here means that one customer can never open another customer's file just by changing an ID in the address.
Then decide which file types the feature really needs. OWASP's examples: for images, allow one agreed type; for CVs, allow DOCX and PDF. It calls blocking a list of dangerous extensions "a weak protection" compared with allowing only the few types you need. It recommends avoiding ZIP files, since they can contain anything.
Checking type, name and size
- Extension: check it against your allow list after decoding the filename. OWASP lists the usual tricks: double extensions such as
.jpg.php, null bytes, mixed case and alternative extensions the server may still run. - Content type: the Content-Type header comes from the user and "cannot be trusted, as it is trivial to spoof". OWASP says it is still a useful quick check against honest mistakes.
- File signature: check the first bytes of the file match the expected type. OWASP warns this should not be used on its own either.
- Filename: OWASP calls generating your own random name, such as a UUID, "essential". If you must keep the original name for display, limit its length and characters and store it separately.
- Size: set a maximum file size. If you unpack or process files, apply the limit after decompression too.
- Content: for images, OWASP suggests decoding and re-encoding to an allowed format and removing unnecessary metadata, using an up-to-date library with resource limits.
Run these checks on the server. Checks in the browser help users, but anyone can bypass them. OWASP also lists protecting the upload form against cross-site request forgery (CSRF) and keeping the libraries that handle files up to date.
Where to store the files
OWASP ranks storage locations by safety:
- On a different host from the application.
- Outside the web root, where only administrators have access.
- Inside the web root with write-only permissions, as a last resort.
It also warns that an attacker may try to upload a web server configuration file such as .htaccess into the upload folder. Files should be served through your app, which maps an ID to a file and checks permissions, never by their original name.
One way to meet the first option is cloud object storage: Amazon S3, Google Cloud Storage or Azure Blob Storage, kept private by default. Which provider fits depends on where the rest of the app runs, as our web app hosting guide explains. If files contain personal data, the storage region and how long you keep files belong in your privacy notice and records.
Direct uploads with signed URLs
Large files do not need to pass through your application server. Instead, the server checks who the user is and what they want to upload, then gives the browser a short-lived link that allows one specific upload straight to storage.
- Amazon S3: AWS's page on uploading objects with presigned URLs says they let someone upload "without requiring another party to have AWS security credentials or permissions". The link carries the permissions of whoever created it. AWS says the expiry can be set as high as 7 days with the CLI or SDKs. Its presigned URL overview adds that a URL can be used multiple times until it expires. If an object with the same key already exists, the upload replaces it.
- Google Cloud Storage: Google's signed URLs page says "anyone who knows the URL can access the resource until the expiration time" is reached or the signing key is rotated. The maximum is 604,800 seconds (7 days), and Google names uploads and downloads as the most common use.
The practical rules follow from those documents:
- keep expiry times short;
- let the server choose the object name, so users cannot overwrite each other's files;
- use a holding area for new uploads until checks have run;
- record each file in your database only after the upload succeeds.
Scanning for malware
OWASP recommends running uploads through an antivirus or sandbox where available. It also suggests Content Disarm and Reconstruct for documents such as PDF and DOCX. AWS and Microsoft offer scanning on upload for their storage services:
- AWS: GuardDuty Malware Protection for S3 starts a scan automatically when a new object lands in a selected bucket. It publishes results to EventBridge and can tag each object with its scan result.
- Azure: Defender for Storage malware scanning scans blobs when they are uploaded or changed. Microsoft says it covers all file types, including archives, up to 50 GB per file, and can soft-delete malicious blobs. It also notes that scan times vary, so apps relying on the result must allow for that.
Whichever you use, design the flow so a file is not shown to other users, emailed on or processed until its scan result is clean.
What to put in the brief
- Who can upload, who can see each file, and whether anything is ever public.
- The exact file types and the maximum size for each upload field.
- Where files are stored, in which region, and for how long.
- Whether files go through the app or directly to storage via signed URLs.
- Malware scanning, and what users see while a file is being checked or if it is rejected.
- How users or rights holders can report a file, which OWASP recommends offering.
For a portal where customers exchange documents with your team, our customer portal planning guide covers the wider plan.
Frequently asked questions
Is checking the file extension enough?
No. OWASP says there is no silver bullet: combine an allow list of extensions with content type and file signature checks, a generated filename, a size limit and, where available, malware scanning.
Where should uploaded files be stored?
OWASP ranks a separate host first, then outside the web root, and inside the web root with write-only permissions last. Private cloud object storage, served through your app with permission checks, meets the first option.
What is a signed URL?
A short-lived link created by your server that lets a browser upload or download one object directly to storage without its own credentials. AWS and Google both allow up to 7 days, and anyone holding the link can use it until it expires, so keep expiry times short.
Do I need malware scanning?
OWASP recommends running uploads through an antivirus or sandbox where available. AWS GuardDuty Malware Protection for S3 and Microsoft Defender for Storage can scan files automatically when they are uploaded.
Building a web app with uploads, portals or document handling? See our web app development service and all our services, or email [email protected].