Skip to content

Security basics

Missing security headers: when it actually matters

Missing security headers is the finding our scanner prints most. Here is what it protects against, and when it is the least urgent line on your report.

Vlad Tkachenko8 min read
The list of headers a site sends back with every page, four of its rows ruled and empty where a value would go.

In short

  • Missing security headers is the most common finding there is, and on its own it says almost nothing about whether anyone can reach your data.
  • We scanned 30,998 live apps. Two in three of the ones missing headers had nothing else wrong at all.
  • It is still worth fixing, and it belongs after the things that decide who can read your database.
  • On some builder domains you cannot set them yourself, and leaving them is a reasonable answer.

Your scan comes back and nearly every line is green. One is amber: some browser safety headers are missing. It is the only thing on the page with a colour, so it reads like the thing to deal with first.

Here is the part most advice on this gets wrong. Missing security headers is the most common finding we print, and on its own it tells you almost nothing about whether anyone can reach your data. Treating it like a breach means either fixing it in a panic ahead of the things that actually decide who reads your database, or learning that amber lines are noise, which is worse.

Between 12 and 14 August 2026 we ran the same nine external checks over 30,998 live apps built with Lovable, Bolt, v0, Replit and Base44. Of the apps this check got an answer from, 30,756 out of 30,981 were missing at least one header. The full numbers are in our scan report.

Are missing security headers a problem?

It is a real gap and it is almost always the least urgent line on your report.

A security header is a short instruction your server attaches to every page it sends, addressed to the visitor's browser rather than to the visitor. Don't let another website put this page inside a frame. Don't guess what kind of file this is. Don't pass my full address along when someone clicks a link away. They are notes to a helper that will do whatever any website tells it.

The rest of your report is about locks. Whether Row Level Security is on, whether a secret key is sitting in your code, whether a storage bucket lists its contents: those decide who can reach your data at all. Notes and locks are both worth having, and the note you never wrote is not how people lose a database.

Our grading treats it that way. A missing header takes five points off a hundred and caps nothing, so an app whose only finding is this one comes back at 95 and a grade A.

What we found across 30,998 apps

Missing headers turned up almost everywhere, and it barely moved with anything else on the report.

25,821 of those apps got an answer from all nine checks, which is the group where "nothing else was wrong" means something. Among them, 25,606 were missing at least one header and 215 had all five.

Apps missing headers were no more likely to have anything else wrong than apps that had all five.

Of the 25,606 apps missing at least one header, 17,043 had nothing else wrong at all. Two in three. It was the only problem on their report.

Now read the other row, which is the part that surprised us. Of the 215 apps that had all five headers set, 145 had something else wrong. That is a higher rate of real problems than among the apps missing headers, not a lower one.

We did not measure why, and the honest reading is narrow: whatever else these scans found, the header finding did not predict it. An app with a perfect set of headers was not a safer app in our data. The likeliest explanation is that setting headers at all means somebody configured a real host, which tends to mean a bigger app with more to get wrong, but that is a guess and we have not tested it.

If you want to see which of these lines your own app produces, the scan is free, takes about 20 seconds and needs no account: scan your app.

What each header actually does

Each one turns off a browser behaviour that is on by default.

HeaderWhat it tells the browserWhat its absence allows
Content-Security-PolicyWhich places this page is allowed to load code and styles fromA script that gets injected can send data anywhere it likes
Strict-Transport-SecurityAlways come back over HTTPS, never plain HTTPA visit on an untrusted network can be pushed down to plain HTTP
X-Frame-OptionsDon't let another site display this page inside a frameYour page can be loaded invisibly inside somebody else's
X-Content-Type-OptionsTrust the file type I gave you and don't guess from the contentsA file someone uploaded can be served as a different kind of file
Referrer-PolicyHow much of this address to pass on when a visitor clicks awayFull URLs, including anything sitting in them, reach third-party sites

Two of those overlap, which the scanner accounts for. A Content-Security-Policy containing frame-ancestors does the same job as X-Frame-Options, so our check counts either one and does not ask for both.

When a missing header is the whole problem

When your app has something worth clicking on while signed in.

That is the clickjacking case, and it is the one that needs no other mistake to work. Someone loads your app in an invisible frame on a page they control and puts their own buttons over yours, so a visitor who is already signed in clicks what looks like their page and hits a control of yours instead: the button that deletes an account, approves a transfer or changes an email address.

With the header, the browser refuses to draw your page inside another site. Without it, that is a supported thing to do.

The visitor is signed in, so the action carries their session. Nothing about your database has to be misconfigured. What makes it work is that the browser was never told to refuse.

Referrer-Policy has a smaller version of the same shape. If a signed-in page of yours has anything identifying in the URL, and it links out to an image host or an analytics script, the whole address travels with the click. Whoever runs that other server sees where your user was.

The other three are conditional. They matter when something else has already gone wrong, and their job is to keep it small. A Content-Security-Policy limits what an injected script can do with its access. X-Content-Type-Options matters if strangers can upload files to you. Strict-Transport-Security covers someone using your app on a network they don't control.

Do I need security headers?

Yes. They are cheap, and they go after the findings that decide who can read your database.

The order that follows from the measurement above: anything critical or high on your report first, headers after. If your report has only this one line on it, as it did for 17,043 of the apps we scanned, then headers are the top of your list by default.

Two of the five cost nothing to get right. X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin are one line each, have no wrong value to choose and cannot break a working app. X-Frame-Options: DENY is also one line, and worth checking first if you deliberately embed your own app somewhere.

Content-Security-Policy is the one that takes real work. A first attempt usually blocks something your own page needed, and the symptom is a feature that silently stops working. Ship it as Content-Security-Policy-Report-Only, which tells you what it would have blocked without blocking anything, and read a week's reports before turning it on.

How to add them

In whatever serves your app to the internet, which is often not the place you build it.

Most builders deploy to a host that owns the response, so the setting lives there. On Vercel it is headers in vercel.json. On Netlify and Cloudflare Pages it is a _headers file at the root of your published output. Behind Cloudflare's proxy it is a Transform Rule. If you run your own server, it is your nginx or Caddy config.

The thing to know about headers once you have set them is that they come off without anyone touching them. Move to a custom domain, put a proxy in front, switch hosts, change a framework config: the headers you added live in one of those places, and any of those moves can leave them behind. Nothing tells you. The page keeps working, and the setting is gone.

That is the sort of thing Reeve Monitor exists for. It re-runs the full check every hour on up to three apps, watches uptime every 60 seconds, and tells you when a result changes rather than waiting for you to look. Monitor watches and nothing more, so if you also want a copy of your database kept somewhere your builder cannot reach it, that is Care, and it covers Supabase.

What to do right now

What to do

  • Read the rest of your report first. If anything is marked critical or high, that is the work, and headers wait.
  • Add X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin today. One line each, nothing to decide, nothing to break.
  • Add X-Frame-Options: DENY if people sign in to your app and can do something consequential in one click.
  • Leave Content-Security-Policy until last, and start it in report-only mode so you find out what it breaks before your visitors do.
  • If you are on a builder subdomain with no header setting, leave the finding alone. It is not yours to fix yet.
  • Check them again after you move domain, add a proxy or change hosts. That is when they disappear.

Headers are worth a quiet afternoon once the rest of the report is clean. If you would rather work through the whole list in order, the 10-minute security checklist covers what to switch off in a newly launched app, and the findings that do decide who reads your database are the ones to clear first.

FAQ

My scan says security headers are missing. Has my app been hacked?

No. A missing header is a setting that was never switched on, and it is not evidence that anything happened. Nothing about the finding involves anyone reaching your data or your account. It describes an instruction your site could be giving visiting browsers and currently is not.

Which security header should I add first?

X-Content-Type-Options and Referrer-Policy, because both are a single line, neither has a wrong value to pick, and neither can break a working app. X-Frame-Options next if people sign in to your app. Strict-Transport-Security after that. Content-Security-Policy last, because it is the only one of the five that takes real thought and the only one that can stop your own scripts from running.

I am on my builder default domain and there is no setting for headers. What now?

Leave them. When your app is served from a platform subdomain, the response headers are the platform choice and not yours, and there is no file you can add that changes them. Connect a custom domain if you want control of this, because the domain is what lets you put your own host or a proxy in front of the app. Until then, spend the effort on the findings that are yours to fix.

Can a Content-Security-Policy break my app?

Yes, and this is the normal outcome of a first attempt. A policy that does not allow inline scripts will stop inline scripts, including ones your builder generated, and the page goes blank or loses a feature with an error only in the browser console. Start with Content-Security-Policy-Report-Only, which reports what it would have blocked and blocks nothing, then read the reports for a week before you switch it on for real.

Do security headers help if my database is readable by anyone?

No. Headers are instructions to the browsers visiting your site, and someone reading your database directly is not using a browser or visiting your site. That request goes straight to your database provider and never touches your pages, so no header on them applies to it. Row Level Security is what decides that one.

Written by

Vlad Tkachenko

Founder, Reeve

I spend my time looking at apps built with Lovable, Bolt, v0, Cursor and Replit, and at the short list of mistakes that keep turning up in them.

More about the author

Read next

All articles

Not sure where your own app stands?

Run a free scan and get a plain-language grade from A to F in about 20 seconds. No account, no card.

Scan your app free

Automated external check, not a full audit. Absence of findings is not a guarantee of safety.