Skip to content
WarHound AI

Application Security

Presigned Uploads Need a Server-Side Completion Check

Presigned URLs offer scalable file uploads directly to storage, but they bypass application-tier input validation. Relying solely on client-side or storage-level constraints leaves systems vulnerable. A robust architecture requires an explicit server-side completion check to validate file signatures, names, and content before marking the upload as processed.

Receive. Validate. Isolate.SEPARATE THE STAGESWARHOUND / FIELD NOTESCONCEPT01RECEIVE02VALIDATE03ISOLATEREVIEW EACH TRANSITIONReceive. Validate. Isolate.SEPARATE THE STAGESRECEIVEVALIDATEISOLATEWARHOUND / CONCEPTUAL ILLUSTRATION
An upload crosses several boundaries, from receiving bytes to storing and serving content. Conceptual illustration.

The Presigned Upload Architecture

Modern web applications frequently delegate file handling directly to cloud storage providers using presigned URLs. This pattern reduces application server load and network bandwidth by allowing the client to send large payloads straight to a storage bucket. The application requests a time-limited, signed URL from the storage API, passes it to the client, and the client performs the upload via a standard HTTP PUT or POST request.

While highly scalable, this architecture inherently bypasses the application server during the actual data transfer phase. Consequently, traditional application-tier validation steps, such as those recommended by the OWASP File Upload Cheat Sheet, are skipped unless explicitly reintroduced. Relying purely on the storage bucket's configuration or client-side checks creates a dangerous blind spot in the application's security posture.

Concrete Worked Example

Consider a hypothetical profile picture upload system. The client requests an upload token. The application generates a UUID for the file, ensuring a safe filename, and generates a presigned URL scoped only to the storage prefix 'quarantine/'.

The client uploads the file to the presigned URL. Upon success, the client sends a completion API request to the application server with the UUID. The application backend then fetches the object from the quarantine prefix.

The backend reads the first few bytes to perform file signature validation, commonly called magic byte checking. If the signature matches a valid image format like PNG or JPEG, and the file size is within limits, the backend issues a storage copy command to move the object to the 'active-profiles/' prefix and deletes the quarantined original. Finally, the application updates the database record to associate the image UUID with the user.

Safe Test Matrix: Allowed and Denied Outcomes

When implementing this completion check, a rigorous test matrix is required to ensure validation logic holds under various conditions. The following hypothetical matrix outlines the expected outcomes for different upload scenarios.

Scenario 1: A file with a .png extension, an image/png content type, and valid PNG magic bytes. Expected outcome: Allowed. The file is promoted to the active directory.

Scenario 2: A file with a .png extension, an image/png content type, but containing PHP script content and lacking image magic bytes. Expected outcome: Denied. The application detects the signature mismatch during the completion check, deletes the quarantined file, and returns an error.

Scenario 3: A file with a .jpg extension, but the size exceeds the configured 5MB application limit during the read stream. Expected outcome: Denied. The file is purged from quarantine.

Scenario 4: A client uploads a valid image to quarantine but never sends the completion API request. Expected outcome: Denied/Purged. A storage lifecycle rule automatically deletes objects in the quarantine prefix after 24 hours.

Implementation Tradeoffs and Failure Modes

Implementing a quarantine-and-promote pattern introduces latency. The completion API call requires the application backend to perform a storage read, process the file in memory or on disk, and perform a storage copy operation. For very large files like video uploads, full file content validation might exhaust backend memory or trigger timeout limits.

A common failure mode occurs when developers only validate the metadata returned by the storage API rather than inspecting the actual file content. Storage APIs often return the exact Content-Type the client supplied during the PUT request. Relying on this metadata defeats the purpose of the completion check. Another failure mode is failing to implement automated cleanup for the quarantine bucket, leading to unbounded storage cost growth from abandoned partial uploads.

Evidence Limits and Threat Mitigation

The OWASP File Upload Cheat Sheet emphasizes that there is no silver bullet in validating user content and that defense in depth is required. Validating file extensions and magic bytes stops trivial bypasses but does not guarantee the file is free of malicious payloads like polyglot images or exploiting vulnerabilities in image parsing libraries. Rewriting images does not guarantee all malicious content is removed.

To further mitigate threats, store files on a different host or outside the webroot, as OWASP advises. Ensure that files are served with headers that force the browser to treat them as downloads or strictly as their intended media type, mitigating stored cross-site scripting risks if a malicious file bypasses the completion check.

Sources and References

The file upload security principles, including the warning against trusting user-provided Content-Type headers, the necessity of file signature validation, and safe storage location practices are sourced directly from the OWASP File Upload Cheat Sheet. Retrieved on 2026-11-12 from: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html

Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.

Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.

Prepared with AI assistance and published by WarHound AI. Check the references and evidence limits in each article.

The WarHoundAI journal

Questions or a factual correction? Get in touch with the article URL and supporting evidence.

Continue reading.

All articles
Application Security

Archive extraction: traversal, symlinks and resource exhaustion

Processing ZIP archives introduces significant risks including path traversal, arbitrary file writes via symlinks, and resource exhaustion. This guide covers safe extraction techniques, testing matrices, and implementation tradeoffs based on OWASP standards.

Read article 6 min read
Application Security

WebSocket authentication and message authorization

Unlike standard HTTP requests, WebSocket connections remain open for persistent bidirectional data exchange, rendering traditional request-based security filters ineffective. Application engineers must implement custom authentication during the initial handshake, continuously manage session validity over long-lived connections, and authorize individual messages independently to prevent hijacking and unauthorized data access.

Read article 6 min read
Database Security

Tenant isolation with PostgreSQL row security

PostgreSQL Row-Level Security (RLS) provides a robust mechanism for enforcing tenant isolation directly at the database layer. By defining permissive and restrictive policies, application engineers can ensure that data access is restricted on a per-user or per-tenant basis prior to application-level filtering. This article details the implementation of these policies, addresses critical race conditions in policy subqueries, and outlines a comprehensive testing matrix to verify isolation boundaries.

Read article 5 min read