Blog · 2026-08-06
CSP, HSTS, nosniff, X-Frame-Options — what security headers actually do
By the howsafeismyapp team
Security headers have a branding problem. The names sound like compiler flags, the tutorials assume you run nginx, and the benefit is invisible — nothing looks different when they work. So most AI-built apps ship without them, and every scan of such an app lights up the same four findings.
Here is the mental model that makes them simple: a security header is an instruction your server sends the browser about what *not* to allow on your pages. The browser already contains the defense; the header switches it on. You are not building anything — you are opting in. (Practical guidance, not legal advice.)
Let's take the four that matter most, in the order we would fix them.
X-Content-Type-Options: nosniff — stop the browser from guessing
Browsers historically tried to be helpful: if a file's declared content type looked wrong, they would sniff the bytes and guess. Helpful until someone uploads a "harmless" file that sniffs as JavaScript and executes in your origin.
X-Content-Type-Options: nosniff tells the browser: trust the declared type, never guess. That closes MIME-sniffing as an XSS route, particularly around user uploads.
- Breakage risk: essentially none for a correctly configured app.
- Effort: one line of config.
- Scanner check: no nosniff header
This is the header to set first, purely because there is no reason not to.
X-Frame-Options — stop your app being framed
Without frame protection, any website can embed your app in an invisible <iframe>, overlay it with fake UI, and trick a logged-in user into clicking your real buttons — delete account, confirm payment — without seeing them. That is clickjacking, and the defense is again one header:
X-Frame-Options: SAMEORIGIN (or, the modern equivalent, a frame-ancestors directive in your CSP) tells browsers to refuse rendering your pages inside other sites' frames.
- Breakage risk: only if something legitimately embeds your app — a widget, an in-app browser preview. If that's you, allow that origin explicitly via
frame-ancestors. - Scanner check: no clickjacking protection
Strict-Transport-Security (HSTS) — no more first-request downgrade
Your app runs on HTTPS. But the *first* time a visitor types your domain, or follows an old http:// link, that initial request can go out unencrypted — and on a hostile network (the classic coffee-shop Wi-Fi), an attacker can intercept and keep the victim on a plain-HTTP impostor.
Strict-Transport-Security: max-age=31536000; includeSubDomains tells the browser: for the next year, never contact this domain over plain HTTP, no matter what the user types. After the first visit, the downgrade window is closed.
- Breakage risk: only if some subdomain still genuinely needs plain HTTP — rare today. Be sure everything runs on HTTPS before setting a long max-age, because the browser will hold you to it.
- Scanner check: no HSTS (or lifetime too short)
Related finding in the same family: individual scripts or images loaded over http:// on an HTTPS page — resources loaded over unencrypted connections. Fix those URLs regardless of headers.
Content-Security-Policy — the big one
CSP answers the question: *which sources is this page allowed to load and execute code from?* Without it, an attacker who manages to inject a script tag — through a stored comment, a compromised dependency, a sloppy innerHTML — gets full run of the page: reading what users type, lifting session data, rewriting the UI.
With a CSP like script-src 'self', injected inline scripts and third-party payloads simply don't execute. It is the single strongest browser-side defense against XSS — and also the only header on this list that takes real work, because you must enumerate what your app legitimately loads.
The path that works:
- Start in report-only mode.
Content-Security-Policy-Report-Onlyevaluates your policy and reports violations without blocking anything. - Browse your app with DevTools open; the console shows exactly what your draft policy would have blocked. Add those legitimate sources.
- Tighten and enforce. Switch the header to
Content-Security-Policyonce the console is quiet.
Frameworks increasingly help — Next.js documents a nonce-based CSP setup via middleware — but even a moderate policy beats none.
- Scanner check: no Content-Security-Policy
Where these headers actually go
No AI builder stack requires a server you manage. Headers live in a config file:
- Vercel — a
headersentry invercel.json, or theheaders()function innext.config.jsfor Next.js apps. - Netlify — a
_headersfile in your publish directory, or[[headers]]blocks innetlify.toml. - Cloudflare Pages — a
_headersfile. - Your own reverse proxy (Caddy, nginx) — a few lines in the site block.
Set them once, globally, and every response carries them. Then reload your app with DevTools → Network, click the document request, and read the response headers — that is the entire verification.
Is this a GDPR thing or a security thing?
Both, which is why our scanner reports them. Art. 32 GDPR requires technical measures appropriate to the risk of processing personal data. Headers that cost one config line and shut down whole attack classes sit near the top of any "appropriate measures" list — and their absence is visible to anyone from the outside, supervisory authorities included. The broader pre-launch picture is in shipping a vibe-coded app safely, and stack-specific passes in the Lovable checklist.
Common questions
Which security headers should I set first?
X-Content-Type-Options: nosniff and frame protection — both are one-liners with near-zero breakage risk. Then HSTS once you're certain everything runs on HTTPS. CSP last, via report-only mode, because it needs tuning to your app.
Will a Content-Security-Policy break my app?
An enforced CSP deployed blind usually breaks something — that's why report-only mode exists. Run the policy in report-only until the violation reports are quiet, then enforce. Nothing breaks during the tuning phase.
Do Vercel or Netlify set these headers for me?
Platforms handle HTTPS well, and some send selected headers on their default domains, but you should not assume coverage — check your actual responses in DevTools. CSP in particular is always yours to define, because only you know what your app legitimately loads.
What about X-XSS-Protection?
Legacy. Modern browsers removed the auditor it controlled, and the header can be ignored or set to 0. CSP is its successor — spend your effort there.
See your headers the way a scanner does
Response headers are public by definition — anyone can read them, so you might as well know what yours say before someone else does. The quickest way is the free Security Headers Check: paste your URL and it reports on exactly the four headers above in a few seconds. Our full passive scan reads them the same way any visitor's browser would and reports them alongside the database, tracking and legal-page checks. About a minute, no login, nothing stored.