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.