Uploads, Storage and Controlled Access
Last updated:
Never serve private files directly
A file at a guessable URL is not access controlled. Private documents must be served through a view that authenticates, authorises and then streams the file.
The most common document security failure in business applications is a PDF at a predictable URL that nobody checks permissions for.
Upload handling
- Store outside the code directory, or in object storage
- Validate content, not the extension or the reported type
- Generate your own filename, never using the uploaded one
- Limit size and type, enforced server-side
- Never build a path from anything a user supplied
Object storage once you scale
| Option | Suits |
|---|---|
| Local disk outside the code directory | Single server, modest volume |
| Object storage | Multiple servers, redundancy, any real volume |
| Database | Rarely — small files only, complicates backups |
Object storage with signed URLs handles both the access control and the serving efficiently, and it removes files from your backup burden.
Generated documents
- Generate in the background, not during a request
- Store what was produced rather than regenerating
- Record which template version produced it
- Never overwrite a document that has been sent
- Retain according to your records policy
An invoice regenerated next year may differ, because the template or a product name changed. The document you sent is the record.
Include files in backups
Uploaded files are frequently outside the database backup and therefore outside the backup entirely. A restore that recovers records and not documents is a partial restore.
Check specifically, and test restoring both together.
Frequently asked questions
How do we serve private files?
Should uploads be scanned?
What about large uploads?
Are uploaded files in our backups?
Documents at guessable URLs?
That is not access control. Serving through a permission-checking view is a contained fix.
Related services
What we build for problems like this one