Specify a secure file-upload journey before building it
Before adding an upload button, specify what a file is for, which files the app will accept, who may submit and retrieve them, and when an accepted file becomes available. This copyable specification matrix follows each file through validation, private storage, scanning, retention and deletion. It makes rejected, delayed and uncertain outcomes part of the requirement instead of leaving them to chance during build.
Start with the job the file must do
Write one sentence for each upload purpose: “An assigned customer contact adds one photograph to an open repair request so the service team can inspect the fault.” Then ask whether the file is necessary. A text description, a link to an existing approved record or a staff-only route may solve the same task with less information to store and protect. An “attach anything” control gives the team no basis for type, size or retention rules.
Name the record the file belongs to and the person or role responsible for it. Distinguish submitted, rejected, held for checking, ready to view and deleted. A file can be received without being approved for download. Treat that distinction as a product promise: the interface should say “received and being checked” until the required checks have actually passed. The broader record and transition design belongs in the state-model guide; this page specifies the file’s own path and handling rules.
Copy the file-upload specification matrix
Make one row per upload purpose, not one row for the entire app. Copy the column order below into a private planning sheet. Fill in actual values with the business owner, privacy or legal reviewer where needed, and the delivery team. Do not put real customer files or names in a public example. The decision column is complete only when it gives a testable rule and names who approves it.
- Purpose and record | Uploader and eligibility | Allowed types and how verified | Count and size limits | Stored name and location | Who may list, preview, download, replace or delete | Checks and release condition | Pending, failed and unknown behavior | Retention, backup and deletion | Owner and acceptance tests.
- Purpose and record: state why the file is needed, the parent record ID and whether a replacement creates a new version or overwrites a prior one.
- Uploader and eligibility: identify the person or service allowed to submit; include record ownership, organisation, assignment and open/closed state where relevant.
- Allowed types and limits: name only formats needed for the task, maximum file count, per-file size and any total or processed-size limit. Decide how a large or interrupted upload is reported.
- Stored name and location: use an application-generated storage key, keep user-supplied names as untrusted display metadata only if needed, and choose storage that is not directly web-accessible.
- Access: define separate rules for upload, list, preview, download, replace, export and delete. Include staff support access and what changes when the parent record is closed or access is revoked.
- Checks and release: specify type and content checks, whether scanning, image rewriting or content reconstruction applies, who or what can release a held file, and what evidence records the decision. Add upload and download request limits and CSRF protection where a browser session relies on cookies.
- Failure and uncertainty: define the message, retry path, support owner and maximum holding period when validation fails, the scanner is unavailable, times out or returns an unclear result.
- Retention and deletion: record the business reason, legal or contractual review owner, event that starts the clock, deletion method, treatment of replicas and backups, and evidence that the policy ran.
Make acceptance checks happen at the server boundary
A file chooser’s accepted-format hint helps people choose, but it is not a security check. OWASP’s File Upload Cheat Sheet recommends an allowlist of business-needed extensions, limits on size, safe generated storage names and validation beyond the submitted Content-Type header, which a sender can spoof. Compare the claimed extension and type with the content expected for that format; a signature check is one layer, not proof that the whole file is safe. Apply the rule on the server even when the browser already checks it.
Decide whether a parser, image decoder or document converter must touch the file. That processing has its own risks and resource limits. A compressed package can expand far beyond its uploaded size; do not accept it merely because the archive itself is small. If the business only needs a photograph, do not enable office documents, archives or executable formats “for flexibility.” For images, decide whether to decode and re-encode the content while removing unnecessary metadata such as location-bearing EXIF; OWASP recommends this as another layer, not proof that the result is safe. Keep the allowlist and processing libraries under review as the use case changes.
Specify request limits on both upload and retrieval to contain abuse, and protect state-changing upload requests against cross-site request forgery when browser authentication uses cookies. These controls complement content checks and per-file authorization; they do not make an unvalidated file safe.
Choose an error message the uploader can act on: “Choose a JPG or PNG image up to the agreed limit” is more useful than “invalid file.” W3C’s form guidance calls for format instructions before input and clear errors connected to the affected control. Make pending, rejected and ready states available to keyboard and assistive-technology users as well as visual users.
Keep storage private and authorize every retrieval
OWASP recommends storing uploads on a separate host where suitable, or outside the web root, and generating the stored filename. The exact storage service is a design choice; the requirement is that knowing a path or object ID must not grant access. Map each file to its parent record, then check the current user’s right to that particular file when it is listed, previewed, downloaded, exported, replaced or deleted. OWASP’s authorization guidance calls for checks on every request and for static resources too.
Specify how a download is served without executing active content in the app’s origin, what happens to a previously issued share link after access is revoked, and whether the original filename may be shown. If a third-party scan service is proposed, record whether files leave the chosen storage boundary and obtain the appropriate privacy and contractual review first. Do not assume that a scanner, a private bucket name or an obscure link settles the access question.
Decide what happens while safety checks are uncertain
OWASP recommends antivirus or sandbox checking where available and content disarm and reconstruction for applicable formats. These are risk-based layers, not a guarantee that a file is harmless. Write which file types receive which checks, the maximum wait, the result recorded and the person who handles a failed or inconclusive check. Keep an unapproved file in a restricted holding area until the release condition is met; it should not appear as an ordinary attachment or enter downstream processing while the decision is unknown.
Specify separate outcomes for a clearly invalid format, detected threat, scanner outage, timeout and a user retry. An outage should not silently turn “pending” into “safe.” If an operator can override a hold, define who has that authority, what evidence they inspect, where the decision is recorded and whether the file may ever be opened in the normal app before review. If the team cannot operate that review path, choose a narrower upload scope or postpone the feature.
Hypothetical example: installation completion photographs
Imagine a small field-installation team planning an internal job app. This is an invented specification example, not Kinetivy client work or a deployed control set. An assigned technician may attach up to three completion photographs to an open job in their organisation. The hypothetical owner chooses JPG and PNG, a maximum of 8 MB per file and a 24-hour maximum scan hold. Those figures illustrate a filled decision and are not universal safe limits. A replacement creates a new version so a supervisor can tell which photograph supported a completion decision.
One illustrative matrix row could read: “Installation evidence; job ID | Active assigned technician, own organisation, open job | JPG/PNG only; server checks extension, claimed type and decoded content | Three photos, 8 MB each; bounded upload/download requests | Generated storage key in private storage | While the job is open, the uploader and assigned supervisor may list, preview and download released versions; after closure only the assigned supervisor may retrieve them until expiry; revocation removes a technician’s access immediately; export and public links denied; uploader may replace while job open; service owner deletes at expiry | Re-encode image, remove unnecessary metadata, complete selected scan; release only after all required checks pass; cookie-authenticated upload has CSRF protection | Hold on scanner outage; at 24 hours reject the held upload, notify uploader and support owner, and require a fresh submission; never release by timeout | Hypothetical live retention: 90 days after job closure, then delete; backup copies expire within a further 30 days | Service owner; U1–U8.” The example periods and controls need business, technical and applicable legal or contractual review before any real deployment.
Turn that row into negative tests. U1: a valid photo on an open job reaches “checking,” then “ready” only after the required checks pass. U2: a renamed executable or mismatched file content is rejected by the server. U3: a file above the limit is rejected without a partial public object. U4: an unassigned technician or another organisation cannot upload to or download from the job, even using a copied direct link. U5: a scanner timeout leaves the file held and inaccessible; at 24 hours it is rejected and the named people are notified, without automatic release. U6: a repeated submission does not create an unexplained duplicate version. U7: after closure the technician cannot retrieve a version but the assigned supervisor can until expiry; revoking a technician’s assignment removes access immediately. U8: expiry removes the live file and metadata access, with backup expiry checked against the hypothetical schedule. Test the actual stored and retrieved result, not only the button label.
Approve the lifecycle, then build the control
Have the business owner read the matrix as a journey: choose a file, receive a clear result, wait if checking is needed, retrieve it under the correct account, and eventually remove it. Have the technical reviewer challenge a fake type, oversized body, copied link, revoked account, scan outage and interrupted upload. The acceptance-testing guide can turn these agreed cases into release evidence. The permission-matrix guide owns the app-wide access policy, while this sheet supplies the file-specific actions and conditions that policy must cover.
Assign a date to review the allowed formats, processing tools, scan service, holding queue and retention behavior. The requirement is complete when the team can explain what happens to a file at every state, including when a dependency is unavailable. An upload feature is not finished merely because a browser shows a filename after selection.
Questions worth asking.
Is an antivirus scan enough to accept any file?
No. Use only business-needed formats and combine server-side type and content checks, resource limits, safe storage, authorization and appropriate scanning. A scan is one layer and can be unavailable or inconclusive.
Can users see a file as soon as the server receives it?
Only if the agreed release checks have finished successfully. If checking is asynchronous, show a distinct pending state and keep the file unavailable to ordinary retrieval and downstream processing until its release rule passes.
How long should uploaded files be kept?
There is no universal duration for every purpose. Name the business need, applicable legal or contractual obligations, event that starts retention, deletion owner and treatment of copies and backups. Approve that policy before collecting files.
Do we need a customer portal before accepting a file?
No. A file may be part of another controlled workflow. The same questions still apply: identify the uploader, parent record, allowed content, checks, storage, retrieval rights and deletion path.
Sources & further reading
- OWASP: File Upload Cheat Sheet ↗︎
- OWASP: Authorization Cheat Sheet ↗︎
- W3C WAI: Form Instructions ↗︎
- W3C WAI: Validating Input ↗︎
- W3C WAI: User Notifications ↗︎
Examples in this guide are illustrative. Read about our editorial approach.