Playbook·check missing-headers

Four security headers that are each one line

What the missing-headers check detects, what each header stops, the config for Next.js, SvelteKit and vercel.json, and why CSP is not a one-liner.

SK Semih Kavaklıoğlu·8 Sept 2026·3 min read Share

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.

The header
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
What it prevents
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

  1. OWASP, HTTP Security Response Headers Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
  2. MDN, Content-Security-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
  3. Next.js, headers in next.config https://nextjs.org/docs/app/api-reference/config/next-config-js/headers
  4. Case, one of our own sites (Next.js on Vercel), all four missing on 8 September 2026, all four present the same afternoon

Share the number

4

response headers a fresh Next.js or SvelteKit app does not send until you add them

moonleap.io · 8 Sept 2026Post

Related