A new site on Vercel comes with HTTPS and HSTS from the host. It does not come with the four headers that stop the most common browser-level attacks, because neither Next.js nor SvelteKit sets them for you. Each one is a line. On one of our own sites all four were missing on day three; the agent added them in one commit.
What does this check detect?
Answer. Responses from the home page and a sample of other pages that lack X-Content-Type-Options, X-Frame-Options (or a CSP frame-ancestors), Referrer-Policy or Permissions-Policy.
Moonleap reads the response headers of the pages it crawls as an anonymous visitor and lists the missing ones. HSTS is checked separately because most hosts add it. A Content-Security-Policy is a separate finding, because it is not a one-liner.
What does each header stop?
Answer. MIME sniffing, clickjacking, referrer leakage, and silent use of device features.
- X-Content-Type-Options: nosniff
- stops the browser guessing a file type
- X-Frame-Options: DENY
- stops your pages being framed by another site
- Referrer-Policy: strict-origin-when-cross-origin
- sends only your origin to other sites
- Permissions-Policy: camera=(), microphone=(), geolocation=()
- disables features you do not use
- nosniff
- a text upload executed as script
- DENY
- clickjacking: your button under a transparent overlay
- Referrer-Policy
- full URLs (with tokens or search terms) leaking to third parties
- Permissions-Policy
- an injected script asking for the camera in your name
None of them costs performance. None of them breaks a normal site. The only one that needs thought is X-Frame-Options: if you embed your own pages in an iframe elsewhere, use CSP frame-ancestors with that origin instead of DENY.
What is the fix?
Answer. Add the four headers at the framework or host layer so every route gets them, then verify with curl.
Next.js, in next.config.js:
module.exports = {
async headers() {
return [{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(), payment=()' }
]
}];
}
};
SvelteKit, in src/hooks.server.ts, after resolve:
const response = await resolve(event);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
return response;
Any framework on Vercel, in vercel.json, with the same four entries under headers for source /(.*). Pick one layer; setting them twice is harmless but confusing.
How do you verify it?
Answer. One curl, four lines.
curl -sI https://<your-domain>/ | grep -iE 'x-content-type-options|x-frame-options|referrer-policy|permissions-policy|strict-transport'
Five lines back means done (the fifth is HSTS from the host). Check one deep page too; a header set only on the home route is a common mistake.
Why is the Content-Security-Policy not on this list?
Answer. Because a CSP has to name every script, style, font, image and connection host your pages use, and one omission breaks the page for everyone.
Ship it report-only first: Content-Security-Policy-Report-Only with your best list, then read the browser console (or a report endpoint) for a week, then rename the header to enforce it. On our own sites the first draft missed the analytics host and the payment iframe; report-only caught both without a single broken page.
FAQ
Do these headers affect SEO or AI visibility?
Not directly. They are part of the security surface Moonleap scans, and a site that leaks referrers or can be framed is a site that gets abused, which does affect reputation.
Should X-Frame-Options be DENY or SAMEORIGIN?
DENY unless you frame your own pages. SAMEORIGIN allows framing from your own domain only, which most apps never need.
Is Permissions-Policy worth it if I use none of those features?
That is exactly when it is worth it: it makes "we do not use the camera" a browser-enforced fact rather than an assumption about every script on the page.
Sources
- OWASP, HTTP Security Response Headers Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
- MDN, Content-Security-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
- Next.js, headers in next.config https://nextjs.org/docs/app/api-reference/config/next-config-js/headers
- Case, one of our own sites (Next.js on Vercel), all four missing on 8 September 2026, all four present the same afternoon