The web application security checklist we run before every launch
Not a compliance document. This is the list we actually work through before a client site handles its first real user.
Uploads should go directly from the browser to object storage using a short-lived presigned URL generated by your server, with the content type and size constrained in the signature. Your server records metadata, validates the file asynchronously, and serves it back through a CDN from a bucket that is never publicly writable.
A presigned URL moves the transfer to a service built for it. Your server does two small things: authorise the upload and record what happened.
Your server signs a time-limited, constrained URL; the browser PUTs the file directly to storage; your server is notified and records the metadata.
// Server: authorise and sign
const key = `uploads/${userId}/${crypto.randomUUID()}.jpg`;
const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket: "user-content",
Key: key,
ContentType: "image/jpeg",
ContentLength: size, // bind the exact size
}), { expiresIn: 300 });
// Client: upload directly, then tell the server
await fetch(url, { method: "PUT", body: file, headers: { "Content-Type": file.type } });
await fetch("/api/uploads/complete", { method: "POST", body: JSON.stringify({ key }) });Above roughly 100MB, use multipart uploads: the client uploads chunks in parallel and can resume after a failure without restarting. Most storage SDKs handle the chunking; you sign the part URLs.
Yes — Cloudflare R2, Google Cloud Storage, Backblaze B2 and most S3-compatible services support equivalent signing, often with the same SDK.
Bind ContentLength in the signature, or use a presigned POST policy with a content-length-range condition. Client-side checks alone are trivially bypassed.
Default to private with signed delivery URLs. Make content public only when it genuinely is, and never leave the bucket itself publicly writable under any circumstances.
Trigger a background job or a storage event on upload, generate derivatives, and store them under predictable keys. Doing it in the request path makes uploads slow and fragile.
ROVQIX Engineering
Engineering team, ROVQIX
The ROVQIX engineering team builds and maintains web platforms, APIs and infrastructure for clients across SaaS, ecommerce and enterprise. These notes come out of real production work — deploys, incidents, migrations and audits.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Not a compliance document. This is the list we actually work through before a client site handles its first real user.
Every request that sends an email, generates a file or calls a third party is a request that should have returned already.
Putting a CDN in front of a site does nothing by itself. The cache headers you send decide whether it helps or just adds a hop.
No spam. Just the occasional case study and craft breakdown.