What the security headers checker looks at
When you paste an address, Keybrum opens it once the way a browser does, follows redirects, and reads the HTTP response headers of the page it lands on. It also asks the http:// version whether it moves to HTTPS, and repeats the request once with another site's Origin header to see whether CORS hands your responses to any website. If you paste an http:// address, the HTTPS version is graded. Page bodies are dropped: only headers are graded.
| Check | What it stops | Points |
|---|---|---|
Content-Security-Policy | Injected scripts (cross-site scripting) running in your page | 25 |
Strict-Transport-Security (HSTS) | A first visit over plain HTTP being sent to a fake copy of the site | 20 |
X-Frame-Options or frame-ancestors | Your page loaded in an invisible frame on another site (clickjacking) | 15 |
| HTTP to HTTPS redirect | Visitors landing on an unencrypted page | 15 |
| Session cookie flags | Session cookies read by scripts, sent over HTTP or sent from other sites | 15 |
| CORS with credentials | Any website reading your responses as the signed-in visitor | 40 |
X-Content-Type-Options: nosniff | Uploaded files or text run as scripts | 5 |
Referrer-Policy | Full URLs, tokens included, sent to other sites | 3 to 5 |
Permissions-Policy | Camera, microphone and location requested by injected code | 3 |
Cross-Origin-Opener-Policy | Other windows keeping a handle on yours | 2 |
Server, X-Powered-By, X-AspNet-Version | Telling a stranger which versions to look up flaws for | up to 6 |
X-XSS-Protection header is not graded: current browsers ignore it.How the grade is calculated
Every site starts at 100. Each missing or weak header takes off up to its points in the table, and the score is what is left. A+ needs 97 or more with nothing marked "fix first" or "worth fixing". A starts at 85, B at 70, C at 50, D at 30, and anything lower is F. A page with anything marked "fix first" gets B at best.
A weak value costs less than a missing one. In the Content-Security-Policy, 'unsafe-inline' or a wildcard in script-src costs 10 of the 25 points and 'unsafe-eval' costs 5. A policy that allows both inline scripts and scripts from any site, or has no script rule at all, stops no injected script, so it costs 20 and counts as "fix first". A policy sent only as Content-Security-Policy-Report-Only costs 20, because it blocks nothing yet. An HSTS max-age under one year costs 8 of 20. A session cookie without HttpOnly or without Secure costs 8 for each flag. Anti-forgery cookies such as XSRF-TOKEN, which your own script reads on purpose, are not asked for HttpOnly.
Two rules override the score. A site without HTTPS gets F. A site that copies any Origin into Access-Control-Allow-Origin, or allows the null origin, and allows credentials gets D at best, because that one is a real data leak rather than missing hardening. On .app and .dev domains, HSTS and the redirect weigh less: browsers already force HTTPS on those whole domain endings, which are on the HSTS preload list.
The weights follow what a stranger can do with each gap. Script injection and HTTPS downgrades lead to stolen sessions. A missing Permissions-Policy or a version number in Server only matters once something else has gone wrong.
How to read the result
- Fix first, in red: gaps a stranger can use today, such as no Content-Security-Policy, a page any site can frame, or a session cookie scripts can read.
- Worth fixing, in amber: the header is there but weak, or the fix is one line, like
nosniff. - Nice to have, in grey: defense in depth, and version numbers in
ServerorX-Powered-By.
Pick your stack above the list and every fix shows its code: next.config.ts, vercel.json, a _headers file for Netlify or Cloudflare Pages, or nginx.conf. The full set in the side panel lists every recommended header in one paste and keeps the values the site already gets right. The raw response headers sit in a collapsible block, with cookie values hidden.
How to add the missing headers
Most hosts take headers from one file. In a Next.js app, add them in next.config.ts:
import type { NextConfig } from "next";
const securityHeaders = [
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), browsing-topics=()" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin-allow-popups" },
];
const nextConfig: NextConfig = {
poweredByHeader: false,
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
export default nextConfig;A Vite app on Netlify or Cloudflare Pages takes the same lines in a _headers file. Put it in public/ so the build copies it into the published folder:
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), browsing-topics=()
Cross-Origin-Opener-Policy: same-origin-allow-popupsOn Vercel without Next.js, the same pairs go in the headers array of vercel.json. On nginx, write add_header Name "value" always; in the HTTPS server block, and remember that a location block with its own add_header drops all of the server block's lines. Only add includeSubDomains once every subdomain serves HTTPS.
Cookie flags and CORS are not host settings. They are fixed in the code that sets the cookie or answers the request, and the checker writes that code for each stack too. For everything else to check in a Next.js app, see the Next.js security guide.
What a header check cannot tell you
This is a static check of one response. It is quick and useful, and it has limits:
- It reads one address. Other pages, API routes and error pages can send different headers, so also check the pages that matter, such as sign-in and the dashboard.
- It reads what the policy says, not what the page does. A Content-Security-Policy that allows a large CDN can still let in a script you never meant to allow.
- Headers are defense in depth. A perfect grade does not stop a stranger from reading your database when row level security is off, or calling your AI endpoint without signing in. Those are the leaks we see most in generated apps: see the vibe coding security checklist and the guide to Supabase RLS.
- Some hosts answer automated requests with a bot challenge. The checker says so when it sees one; compare with the headers in your browser's network tab.
Knowing whether a stranger can actually use a gap takes an attack, not a list of headers: that is the difference between a scan and a penetration test. Keybrum's full attack runs this header baseline on your app and the API origins it calls, next to more than 100 other attacks.
Questions
How do I check the security headers of my website?
Paste the address into the security headers checker above: it fetches the page once and grades every header, with the fix for your stack. From a terminal, curl -sI https://your-app.com prints the same response headers, and your browser's network tab shows them for any page you open.
What is a good security headers score?
A or A+ means the page sends every header that matters, with strong values; an A can still allow 'unsafe-inline' in its script policy. B means the basics are there with one real gap, most often no Content-Security-Policy or a page any site can frame. D and F mean several gaps a stranger can use, such as missing HSTS, a session cookie scripts can read, or no HTTPS at all.
Is this a CSP checker too?
Yes. It reads the Content-Security-Policy header and any policy set in an HTML meta tag, flags 'unsafe-inline', 'unsafe-eval' and wildcard script sources, checks frame-ancestors, and tells you when a policy only reports. It does not run your page, so it cannot list which of your own scripts a policy would block: Report-Only mode in the browser does that.
Which HTTP security headers does every site need?
For an app that people sign in to: Content-Security-Policy, Strict-Transport-Security, X-Frame-Options (or frame-ancestors), X-Content-Type-Options: nosniff and Referrer-Policy. Permissions-Policy and Cross-Origin-Opener-Policy add depth at almost no cost. Session cookies also need Secure, HttpOnly and SameSite.
Does X-Frame-Options still matter if I set frame-ancestors?
Browsers that understand frame-ancestors ignore X-Frame-Options when both are set, so frame-ancestors decides. Keep both and make them agree: X-Frame-Options still covers older browsers, and pages where the policy sits in an HTML meta tag, where frame-ancestors does not work.
Can I check a site I do not own?
The checker sends a few ordinary GET requests, the same a browser sends when it opens the page, with no attack payloads, and stores nothing. Keybrum's attacks are different: they only run on apps whose owner has proved ownership.