A file upload crosses a larger trust boundary than a short text field. The browser can send multipart data, but the filename, declared media type, and bytes are all caller-controlled. The server must authenticate and authorize the case, cap request size before buffering, inspect the actual file format, assign a generated storage name, and keep untrusted bytes away from executable paths. If scanning or conversion runs later, the record should show a pending state until the file is ready. Do not publish a raw upload at a predictable path merely because the filename ends in an allowed extension.
Safe File Upload Pipeline
Working case
A reviewer uploads a pump image for case 47. The page's native multipart form still works if JavaScript fails. The server rejects a 29-megabyte file when the policy allows 8 megabytes, even if the browser input accepts it. It checks reviewer permission and the decoded image type, stores it under a generated ID, and creates a separate display variant. A file that claims to be JPEG but contains another format is not trusted. If later processing fails, the case record retains a clear pending or failed attachment state instead of a broken public link.
Implementation
<form action="/cases/47/photos" method="post" enctype="multipart/form-data">
<label for="inspection-photo">Inspection photo</label>
<input id="inspection-photo" name="photo" type="file" accept="image/jpeg,image/png" required>
<button type="submit">Upload photo</button>
</form>Cost and boundaries
Upload transfer, virus scanning, and image decode are O(B) or more for B input bytes; decoded images can consume far more memory than their compressed file size. Streaming to bounded temporary storage prevents one request from holding the whole file in process memory. Generated variants add storage but reduce repeated download cost. Rate-limit upload endpoints separately from simple reads. Keep retention and deletion rules so abandoned files do not accumulate indefinitely.
Common Mistakes
- Do not trust the browser-supplied Content-Type or filename alone.
- Do not buffer an unlimited file before checking size.
- Do not serve unverified uploads from an executable public directory.
Connected lessons
Media and Live Updates; Image Variants and Delivery Budgets; Server-Sent Events or WebSocket for Live Views; Live Update Reconnect and Event Ordering; Page loading: keep content available while CSS and scripts arrive; Fetch requests: separate HTTP failure, transport failure, and cancellation; HTML srcset and sizes: let the browser choose an appropriately sized image.
Failure trace
A browser sends a file named inspection.jpg whose bytes are not an image. The server trusts the extension and serves the upload from the same origin as the application. A malicious payload can then reach users through a trusted route. Enforce size and type rules on the server, inspect the file content, store it outside executable paths, and generate safe derivatives before public delivery.
Verification
- Upload a file with an image extension and non-image bytes; require rejection.
- Submit an oversized body and confirm it is stopped before expensive decoding or storage.
- Request the stored file as another user and verify authorization or public-access policy.
Decision note
Every accepted file has processing and storage cost. Quarantine or reject uncertain content, strip unneeded metadata where appropriate, and keep an audit trail from upload ID to generated variants.
Apply and check
Build Project: live review board with ordered status; then check the boundary with Web Development: offline and delivery contracts quiz.
Advanced connections
Outbound URL Requests and SSRF Boundaries.
Further connections
Multipart Upload Progress and Retry Boundaries.
Further connections
File Ingestion and Private Asset Lifecycle; Upload Intake Budgets and Storage Ownership; File Quarantine, Inspection, and Derived Previews.
