An uploaded file is untrusted input
Return photos, artwork for personalisation, B2B documents and support tickets turn file upload into a commercial feature. They also make every attachment untrusted content: its name, extension and declared media type come from the client and do not prove that the bytes are safe. The risk is not limited to malware. A file may target a vulnerable parser, consume storage, exhaust a scanner, overwrite an existing object or become public before controls finish.
The answer is not a single antivirus product but a defence-in-depth pipeline. As a fail-closed design choice, we recommend separate states for authorisation, application constraints, private storage, quarantine, analysis, release and download. In this model, each stage accepts an object only after the previous outcome and retains enough evidence to explain who submitted it, for which business record, and under which version of the policy.
Define scope and policy before writing code
Classify flows and consequences
Build an inventory for every use case: required extensions, maximum size, quantity per request, authorised users, retention, destination and intended recipients. A return photograph, a tax PDF and a source file for customised printing do not have the same profile. Avoid a universal endpoint with a permissive list. Bind an explicit policy to the business context and record which policy version handled the object.
Define at least states such as requested, uploaded, quarantined, accepted, rejected and expired. Only accepted may become available to a recipient. Scanner errors, timeouts or outages are not clean outcomes: the object remains isolated while the system decides whether to retry or request another upload. This state model prevents a failure from becoming a shortcut around quarantine.
Validate intent and bytes in layers
Allowlists, limits and generated names
Use an allowlist of extensions that the specific journey genuinely needs, and apply it after normalising the name, including case, double suffixes and ambiguous characters. The browser-supplied Content-Type is useful as a signal but is not trustworthy on its own. Compare it with the extension, signature or magic bytes and the result of a format-aware parser. Even a consistent result does not make the file harmless; it reduces unexpected input before the next controls.
Enforce limits before and during transfer, and reject partial content or objects beyond the policy. Never use the original name as the storage key. Generate a random, unpredictable identifier, keep the submitted name only as normalised metadata, and block traversal, path separators and collisions. Confirm that the order or case belongs to the authorised user, and protect upload initialisation from cross-site actions according to the application architecture.
Separate transfer, storage and authorisation
Direct upload with minimum permissions
A presigned URL can let the browser send an object straight to storage without routing its bytes through the application server. It is not a complete security decision: a holder can use the URL within its conditions and lifetime, with the permissions of the identity that created it. Issue a short-lived URL for one new key and one intent, only after authorising the user, order or ticket.
The intake location must be private and separate from the served destination. Do not let the client freely choose a bucket, a public path or the definitive object key. Uploading to an existing key can replace its object, so generated uniqueness prevents that collision. On completion, the backend verifies presence, size and expected metadata, associates the object with its business record and moves it into quarantine without creating a public URL.
Design the quarantine and scanning workflow
Make every result an operational state
One operational implementation uses an isolated worker that reads the object with narrowly scoped credentials, calculates a hash, identifies its real type and runs the required controls. An engine such as ClamAV can run as a process or service, but it needs maintained signatures and explicit limits for archives, recursion, time and expanded size. Where the format and risk justify it, normalisation or content disarm and reconstruction can reduce active content; it does not replace scanning or validation.
Record the engine and signature version, hash, duration, outcome and an internal rejection code. Do not expose diagnostics that would help someone tune a payload. A detection moves the object to a non-deliverable state and starts the appropriate procedure; a technical error remains quarantined. Avoid unlimited retries: use a bounded policy, an error queue and an alert when the age or volume of isolated objects exceeds internal thresholds.
Deliver only accepted objects
Mediated downloads and coherent headers
As a fail-closed design choice, the transition to accepted should be atomic and auditable, not an implicit client-side copy. For private documents, authorise every download against account, order, role and expiry, then create a temporary link or mediate the stream. Send a Content-Type that matches the verified format and consider Content-Disposition: attachment when content should download rather than render inline. The proposed filename remains advisory and must be sanitised.
X-Content-Type-Options: nosniff blocks a request whose destination is script or style when its declared MIME type is invalid for that destination; for other response types it disables MIME sniffing. It is one defence layer, not an attachment scanner. Protection comes from private storage, authorisation, verified type and controlled responses together. If some media must be public, publish a regenerated derivative through a separate path rather than the untouched original, and prevent execution on the application origin.
Measure security and reliability together
Observe initialised requests, completed uploads, received bytes, policy rejections, mismatched types, scan outcomes, errors, timeouts and time spent in quarantine. Segment by business flow and policy version, not by unnecessary personal data. Add capacity signals: queue depth, oldest-object age, occupied storage, retries and the proportion of allowed or denied download requests.
Connect events with technical identifiers that contain neither original names nor secrets. A rise in errors may indicate stale signatures, an unstable parser, abuse or a legitimate client change. Dashboards and alerts should let operators suspend a file type, stop new initialisations or retain everything in quarantine without losing objects and without opening an uncontrolled alternative route.
Test incidents, deletion and recovery
Test empty files, double extensions, mismatched media types, protected or deeply nested archives, interrupted transfers, objects above limits, collisions, unavailable scanners and downloads by unauthorised accounts. Confirm that no outcome other than clean makes an object deliverable. Test expiry, deletion, audit and recovery as well. As another fail-closed design choice, configure recovery so that restoring a backup does not automatically reintroduce a rejected or expired attachment.
A robust pipeline makes every boundary visible: the client proposes, the application authorises, storage isolates, controls classify and delivery decides access. To design storage, queues and controls around your commerce architecture, explore our ecommerce systems services. If scattered upload features need a verifiable operating model, contact AE Digital Agency to define policy, quarantine and release criteria.
Frequently asked questions
Do extension and Content-Type checks make an uploaded file safe?
No. Both are useful signals but can be controlled by the client. Compare them with signatures, real type and a parser, then add limits, private storage, quarantine, scanning and authorised delivery.
Does a presigned upload URL require public storage?
No, but it grants its holder the stated operation within its lifetime and conditions. Create it only after authorisation, scope it to a unique key and keep its expiry appropriately short.
What should happen when the malware scanner is unavailable?
The file must not be treated as clean. Keep it in quarantine with an error state, apply bounded retries and alert operators when it breaches the defined operational thresholds.
How should an accepted ecommerce attachment be delivered?
The backend verifies the account, order or role, then issues a temporary URL or mediates the stream. The response uses a verified media type, a sanitised name and suitable Content-Disposition.
Related articles
Ecommerce Content Security Policy: a safe rollout for checkout and analytics
An implementation guide to introduce CSP without breaking payments, consent or measurement: dependency inventory, Report-Only, nonces, journey tests and canary enforcement.
Passkeys for ecommerce: a controlled rollout across login, checkout and account recovery
An operational runbook for adding passkeys without blocking guest checkout or existing users, covering WebAuthn flows, fallback, recovery, metrics and progressive rollout.
Ecommerce disaster recovery: RPO, RTO and restore testing
Turn scattered backups and procedures into a measurable ecommerce recovery plan that is tested end to end and tied to business priorities.
