Secure file uploads: from the form to storage

A PDF extension and a successful upload are not enough. Design quarantine, content checks, private links and permissions for business portal attachments.

Paper, a pen and a keyboard on a white work desk

A file starts as an untrusted input

In an illustrative customer portal, a user attaches a PDF to an order. The browser sends invoice.pdf and MIME type application/pdf. The sender can change both values. If the server saves the attachment in a public directory and immediately shares it, other users can access the content before validation. Uploading, accepting and permitting download therefore need distinct states.

Start with formats that the process actually needs. A system requiring PDF and JPEG does not automatically need HTML, SVG or archives. OWASP recommends combining extension, content, permission and storage controls. No individual check covers every problem. A successful antivirus scan also does not establish which order the file belongs to or who may read it.

Private quarantine separates receipt from availability

First verify the user, their right to add attachments and the target order. Create an upload record with a server-generated random identifier and a size limit. Keep the original filename as display metadata if needed, but do not use it as a storage path. Content enters a private location from which the application issues no normal download links.

After upload, check actual size, format and content integrity. For images, also bound pixel count and decoding cost. Process documents using maintained tools in an isolated environment with time and memory limits. A large compressed or damaged input must not exhaust the application server. Rejecting unnecessary archives is simpler than implementing unrestricted extraction.

A signed URL does not validate the file

For direct Amazon S3 uploads, a server can issue a short-lived presigned URL for a specified object. Its holder has the corresponding ability while it remains valid; the URL is not single-use. Uploading to an existing key can replace its content. Generate a separate key for each attempt, verify the resulting object and address the possibility of another write.

An illustrative design can validate a specific object version and publish that exact version, or copy verified content into protected final storage. The ready state must refer to the content that validation actually checked. Otherwise the sender could upload different content under the same key after scanning. A checksum identifies the checked content; it does not establish that the content is safe.

The server controls completion

A client's complete call announces that upload has ended. The server independently verifies object existence, ownership and state. The user cannot submit another customer's arbitrary storage key or set their own scanPassed value. Interrupted uploads remain incomplete, and a cleanup process removes unused objects and records after an agreed period.

Move to the ready state only after every required check. Duplicate completion must not create two attachments. Concurrent order deletion needs an explicit outcome, such as rejecting completion and cleaning up the object. Design database state and storage lifecycle together. Otherwise the system accumulates orphaned files or references to missing content.

Check permission again when downloading

Knowing an attachment ID or hidden URL does not grant access. At download, verify current membership, the order and attachment state. If the server issues a temporary URL, consider its validity and revocation behaviour: an already issued link may remain usable until expiry. Documents requiring immediate revocation may need every access mediated by a controlled endpoint.

Content-Disposition: attachment asks the browser to offer a download. X-Content-Type-Options: nosniff restricts content-type guessing. These headers support safe delivery without replacing input validation. Do not serve active user content as HTML on the administrator interface's origin. Encode the displayed filename appropriately so it cannot become an unchecked header or HTML input.

Test difficult inputs as well

Test files should cover rejection, interruption and concurrency. Choose limits from actual document needs and available resources; a number copied from another project may not fit. Also verify restoring attachments alongside database relationships and looking up validation outcomes without recording the sensitive content itself.

  • Renamed unsupported content does not become accessible merely because it has a PDF extension.
  • A user from another tenant cannot complete or download an unrelated attachment.
  • A repeated upload after validation cannot change content available for download.
  • An interrupted upload is cleaned up without deleting a valid attachment.

Sources and documentation

For implementation, consult the documentation for the version you use.

Put the topic into practice.

Have a process
that needs to change?

Let’s start with how you work today. We’ll choose the technology around it.

Discuss your project