Security Headers for Web Applications: Complete Setup Guide
Configure essential security headers for your file transfer web app. CSP, HSTS, X-Frame-Options, and more headers that protect against common web attacks.
Security headers are HTTP response fields that tell browsers how to constrain a page's behavior. For a file transfer app doing client-side AES-256-GCM encryption, six headers matter most: Strict-Transport-Security to force HTTPS, Content-Security-Policy to block script injection, X-Frame-Options to prevent clickjacking, Referrer-Policy to stop URL fragment leaks, Permissions-Policy to disable unused browser APIs, and Cross-Origin-Opener-Policy to isolate the browsing context. Get these right and you block 80% of practical client-side attacks without changing a line of JavaScript.
HSTS and the preload list
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload tells browsers to never connect over plain HTTP again, for two years. The preload directive qualifies you for Chrome's HSTS preload list (hstspreload.org), which ships with the browser — even the first request to your domain goes over HTTPS, eliminating the initial MITM window. Submission is a one-way street; removal takes months. Test with max-age=300 for a few days first. A file transfer service without HSTS preload is one DNS hijack away from serving a fake upload form.
Content Security Policy that actually works
CSP is the hardest header to deploy and the most valuable. A strict policy for a file transfer app looks like: default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' https://upload.hexatransfer.com; style-src 'self'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'. No inline scripts, no eval except wasm, no third-party connections. Move any inline event handlers to addEventListener in external files. The 'wasm-unsafe-eval' is needed for libsodium.js; without it, Argon2id won't instantiate.
Subresource Integrity on every bundle
If your JavaScript loads from a CDN like jsDelivr or unpkg, add integrity="sha384-..." attributes on every script tag. The browser refuses to execute scripts whose hash doesn't match — a compromised CDN can't silently push a backdoored bundle. For self-hosted scripts served from your own domain, SRI is less critical but still defensible. Pair SRI with CSP's require-sri-for script style (deprecated but Chrome still honors it) or enforce via build pipeline. HexaTransfer publishes per-release hashes so paranoid users can verify manually.
X-Frame-Options and frame-ancestors
Clickjacking attacks load your upload page in a transparent iframe over a different page, tricking users into clicking "send" on a file they didn't mean to share. X-Frame-Options: DENY blocks all framing; the modern replacement is Content-Security-Policy: frame-ancestors 'none'. Ship both — older browsers honor only the legacy header, newer ones prefer CSP. Never use SAMEORIGIN for a file transfer app; you have no legitimate reason to frame upload UI from another page.
Referrer-Policy to stop fragment leaks
Browsers strip URL fragments (the #key=... part where your decryption key lives) from Referer headers by default, but the path portion still leaks. Referrer-Policy: no-referrer blocks all referrer information — no Referer header on outbound links, no cross-origin leaks, no URL fingerprinting by external trackers. For a download page at /d/7Kj9xQmN2vP8rBwLsE4fT#k=abc, this prevents the slug from leaking to any external domain the user clicks through to. Set it site-wide via header; don't rely on per-link rel="noreferrer".
Permissions-Policy for defense in depth
If your app doesn't use the camera, microphone, geolocation, or USB API, explicitly deny them: Permissions-Policy: camera=(), microphone=(), geolocation=(), usb=(), bluetooth=(), accelerometer=(), magnetometer=(), gyroscope=(), payment=(). A XSS that slipped past CSP still can't turn on the webcam to record the user's face. This header is cheap to deploy, has no UX impact for a file-transfer workflow, and signals to security reviewers that you've thought about sandboxing.
Cross-Origin isolation headers
For access to high-resolution timers and SharedArrayBuffer (needed for some wasm crypto implementations), browsers require cross-origin isolation via Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. These headers also prevent Spectre-style side-channel attacks that could leak AES keys from adjacent origins. The tradeoff: embedded third-party resources (YouTube videos, Stripe checkout) break unless they serve Cross-Origin-Resource-Policy: cross-origin. For a single-purpose file transfer app with no third-party embeds, isolation is free.
Cache-Control for sensitive pages
The download confirmation page might display a short-lived decryption key or session token. Prevent caching: Cache-Control: no-store, must-revalidate and Pragma: no-cache. Don't rely on just no-cache — that allows revalidation against the origin, which means caching still happens. For the upload page's JavaScript bundle, the opposite applies: long max-age with content-hashed filenames (/js/main-a7b3c9.js) so CDN caching works without invalidation headaches. Apply headers surgically per path.
X-Content-Type-Options and MIME sniffing
X-Content-Type-Options: nosniff stops browsers from guessing MIME types based on content. Without it, a .txt file uploaded by an attacker containing <script> could be rendered as HTML when served back. For a transfer service that serves user-controlled downloads, combine nosniff with Content-Disposition: attachment; filename="..." so the browser downloads instead of rendering. Validate filename against path traversal (../../../etc/passwd) server-side; don't trust the upload's metadata for anything but display.
Monitoring and CSP reports
Deploy CSP in report-only mode first, collect violations at a report-uri endpoint for a week, fix legitimate issues, then enforce. Use Content-Security-Policy-Report-Only for observation, switch to Content-Security-Policy for enforcement. The report endpoint gets JSON POSTs describing each violation: blocked URI, violated directive, source file, line number. Services like Report URI or a self-hosted sentry collect these. Review weekly — genuine attacks show up as unusual blocked URIs you've never seen before.
Grading your work
Run your production URL through securityheaders.com and Mozilla Observatory. An A or A+ grade isn't perfection but it's table stakes. Most file transfer competitors score B or worse because they forget Permissions-Policy or allow 'unsafe-inline' in CSP. A small service can easily beat WeTransfer's security header score; the headers cost nothing to add, and the audit trail for "why isn't your CSP strict" conversations with SOC 2 auditors is shorter when you started strict.
Try it at hexatransfer.com — free, no account, 10 GB max.
Send large files securely with end-to-end encryption
Transfer files up to 10 GB for free with end-to-end encryption. No account required. Your files are encrypted in your browser before upload — no one else can read them.
Send a file