SECURITY HEADERS CHECKER

Paste your URL. We grade every security-relevant HTTP header and show exactly how to fix the misses — copy-paste config for Vercel, Netlify, Nginx, and Express.

GRADE YOUR SECURITY HEADERS NOW

Enter your URL — we fetch every security-relevant HTTP header and return Pass / Warn / Fail with paste-ready fix configs for Vercel, Netlify, Nginx, and Express.

Why Headers Matter

HTTP security headers are the last line before the browser. A missing X-Frame-Options means your login page can be loaded inside an attacker’s iframe and clickjacked. Missing Strict-Transport-Security means your users’ first visit over plain HTTP can be downgraded and the session cookie sniffed. Permissive CORS means other sites can read your authenticated responses with the user’s cookies attached.

The headers themselves are simple key-value strings — they fix entire vulnerability classes for the cost of one config block. They’re also the cheapest, fastest hardening pass an app will ever get.

For AI-generated apps on Vercel, Netlify, Railway, or Cloudflare Pages, headers are almost always missing on day one. The generator ships a working UI; the host serves it with framework defaults; nobody adds vercel.json / _headers / Helmet until a scanner fails a free check. That is exactly the niche this tool fills.

The Grade Card

Each header gets scored Pass / Warn / Fail with specific guidance:

STRICT-TRANSPORT-SECURITY

Prevents protocol downgrade. Need max-age=31536000; includeSubDomains; preload.

CONTENT-SECURITY-POLICY

The big one. Blocks XSS, inline scripts, and unauthorised origins.

X-FRAME-OPTIONS / FRAME-ANCESTORS

Clickjacking defense. Explicit DENY or CSP frame-ancestors 'none'.

CORS (ACL-ALLOW-ORIGIN)

Never * with credentials. Explicit origin whitelist only.

X-CONTENT-TYPE-OPTIONS

nosniff — stops MIME-confusion attacks where a JSON response gets executed as JS.

REFERRER-POLICY

Limits what third parties learn about your URLs. strict-origin-when-cross-origin is the safe default.

PERMISSIONS-POLICY

Disables camera, mic, geolocation, payment APIs by default — re-enable per surface that needs them.

COOKIE FLAGS

Session cookies need Secure; HttpOnly; SameSite=Lax. Most frameworks miss at least one.

Header reference table

Header Recommended value If missing
Strict-Transport-Security max-age=31536000; includeSubDomains; preload First request can be MITM’d over HTTP
Content-Security-Policy default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none' (then tighten per route) XSS payloads load remote scripts
X-Frame-Options DENY (or rely on CSP frame-ancestors) Clickjacking via hidden iframe
X-Content-Type-Options nosniff Browser executes a text/plain response as JS
Referrer-Policy strict-origin-when-cross-origin Full URLs (with tokens in query strings) leak via Referer
Permissions-Policy camera=(), microphone=(), geolocation=(), payment=() Embedded third parties can request sensitive APIs
Cross-Origin-Opener-Policy same-origin Spectre-style cross-window attacks possible
Cross-Origin-Resource-Policy same-origin (or cross-origin for public assets) Other sites can embed your authenticated responses
Cache-Control (auth pages) no-store Sensitive HTML cached on shared proxies / browsers

HSTS details that trip people up

  • max-age under 1 day grades poorly — browsers barely remember the policy.
  • includeSubDomains without HTTPS on all subdomains bricks HTTP-only internal tools on those hosts. Inventory subdomains first.
  • preload means you intend to submit to the HSTS preload list; do not set it until every subdomain is HTTPS-ready.
  • HSTS only helps after the first successful HTTPS response. Users who type http:// on first visit still need redirects; the checker assumes you already force HTTPS at the edge.

CSP directives that matter for vibe-coded apps

Directive Common AI mistake Safer pattern
script-src 'unsafe-inline' 'unsafe-eval' forever Nonces/hashes; avoid eval
connect-src Forgot Supabase / Stripe API hosts Allow only required APIs
img-src Over-open https: Limit to CDN + self
frame-ancestors Missing entirely 'none' or known parents
base-uri Missing 'self' stops base-tag hijacks
object-src Missing 'none' blocks plugins

How attackers find this

Header misconfigurations are trivially scanned. There are public scanner indexes (securityheaders.com, Mozilla Observatory, plus dozens of “security report” SaaS bots) that crawl deployed sites continuously and publish grades. Attackers don’t need to scan you — they just query the index.

For specific exploits:

  • Missing HSTS — sslstrip-style downgrades on hostile networks (open Wi-Fi, hotel captive portals). The first request a user makes to yoursite.com is plain HTTP; the attacker rewrites the redirect.
  • Missing X-Frame-Options — attacker hosts attacker.com/win-a-prize, embeds yoursite.com/transfer-funds in a transparent iframe over a “click here” button. User clicks button on attacker site, actually clicks the transfer button on your site, with their cookies attached.
  • Access-Control-Allow-Origin: * with credentials — when paired with Allow-Credentials: true (browsers reject this combo, but bad servers send * anyway and developers then “fix” it by reflecting the Origin header without validation), any site can read authenticated responses.
  • Missing CSP — every reflected XSS becomes a full account takeover, because the injected script can call any origin and exfiltrate cookies/localStorage.
  • Server and X-Powered-By headers — version disclosure. Attacker reads Express 4.17.1, looks up CVEs for that version, knows exactly which exploits apply.

Reflected Origin is the AI-favorite CORS bug:

// Dangerous "works with any localhost port" fix
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");

Allowlist origins instead. See also CORS credentials misconfig.

Host-Specific Recipes

The report includes paste-ready config for every target. Common ones:

vercel.json:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(), payment=()" },
        { "key": "Content-Security-Policy", "value": "default-src 'self'; script-src 'self' 'nonce-{NONCE}'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" }
      ]
    }
  ]
}

Netlify _headers:

/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
  Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Express + Helmet:

import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
      objectSrc: ["'none'"],
      baseUri: ["'self'"],
      frameAncestors: ["'none'"],
    },
  },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
  crossOriginOpenerPolicy: { policy: 'same-origin' },
}));

Nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;

The always flag is mandatory — without it Nginx skips the header on error responses, which is exactly when version-disclosure leaks happen.

Next.js headers() in next.config.js:

async headers() {
  return [
    {
      source: "/:path*",
      headers: [
        { key: "X-Content-Type-Options", value: "nosniff" },
        { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
        { key: "X-Frame-Options", value: "DENY" },
      ],
    },
  ];
}

If both CDN Transform Rules and origin set CSP, pick one owner. Duplicate policies confuse debugging when staging “works” and prod fails.

CSP rollout strategy

CSP is the one header that will break things if you turn it on cold. Roll out in three stages:

  1. Report-only — deploy Content-Security-Policy-Report-Only with report-uri /csp-report. Browsers send violation reports; nothing is blocked. Run for 1–2 weeks across your real traffic.
  2. Triage reports — every violation is either a real script you missed in the policy (whitelist with hash/nonce) or an actual XSS attempt (good, it would have been blocked).
  3. Enforce — promote to Content-Security-Policy. Keep report-uri so regressions surface in CI.

If you can’t host a report endpoint, csp-evaluator.withgoogle.com will at least flag obvious holes in a pasted policy.

Nonces vs hashes vs 'unsafe-inline'

  • Nonces work well with SSR frameworks that can inject a per-request random value into script tags and the CSP header.
  • Hashes fit static inline scripts that never change.
  • 'unsafe-inline' is what AI generators leave forever after an analytics paste. Treat it as temporary debt with an expiry.
<!-- Server injects the same nonce in header and tag -->
<script nonce="rAnd0m">window.__BOOT__ = {}</script>

Why AI-Generated Apps Fail This

AI generators ship the app first, headers later — or never. Default framework output ships without CSP. CORS gets set to * “temporarily” during debugging and stays. The dev environment never triggered any of these as bugs because everything was same-origin localhost. This checker is the fastest way to catch those drifts.

Typical AI excuses that stay in production:

  • “We’ll add Helmet later.”
  • Access-Control-Allow-Origin: * to unblock a Vite port.
  • Inline analytics snippets forcing 'unsafe-inline' forever.
  • Marketing page and app sharing one origin without split CSP.
  • Middleware that sets headers only on HTML pages, leaving JSON APIs bare.
  • Preview deployments that skip vercel.json headers until custom domain production.

Cookies the checker cares about

Headers and cookies interact:

Flag Why
Secure Cookie only on HTTPS
HttpOnly Not readable from JS (XSS impact reduction)
SameSite=Lax or Strict CSRF reduction
Path / Domain scope Over-broad Domain leaks cookies to sibling apps

AI session code often sets SameSite=None; Secure “so mobile WebViews work” without needing it — widening CSRF surface. Prefer Lax unless you have a documented cross-site cookie need.

How to verify after fixing

curl -sI https://yoursite.com | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'
  1. Redeploy with header config.
  2. Re-run this checker on production and preview URLs (they can differ).
  3. Click through login, payments, and any third-party widgets — CSP breakages show as blank widgets or console errors.
  4. Confirm authenticated pages send Cache-Control: no-store where needed.
  5. Still run Token Leak Checker and Vibe Code Scanner — headers do not catch open APIs.
  6. Check an error page (404/500) still includes security headers (always on Nginx; framework error handlers on Node).

Framework notes

Stack Where to set headers
Next on Vercel vercel.json or next.config.js headers()
Netlify _headers or netlify.toml
Express helmet middleware
Nginx / Caddy server block add_header / header
Cloudflare Transform Rules (remember origin may still need headers)
Railway / Render reverse proxy or app middleware — platform rarely sets CSP for you

If both CDN and origin set CSP, the browser uses the response it receives — duplicate conflicting policies are a common “works on staging, fails on prod” issue. Pick one authoritative layer.

What this scanner does NOT flag

  • Headers set on a different domain. If your assets are on a CDN subdomain that returns its own headers, we test only the URL you submit. Pass each origin separately.
  • Edge-case CSP issues. A policy that parses correctly but allows 'unsafe-eval' for a single legitimate library is graded Warn, not Fail — the call is yours.
  • Cookie issues for cookies the scanner never sees. We grade the cookies your site sets on a fresh visit. Cookies set only after login require an authenticated re-scan.
  • Header order or duplicates from upstream proxies. Some load balancers prepend their own X-Frame-Options. We report the value the browser actually receives; if your app sends DENY and the proxy rewrites it to SAMEORIGIN, the scanner shows the rewritten value.
  • Business-logic auth failures. Perfect headers, open Supabase tables — still critical elsewhere.

CI and release gates (1)

Encode the minimum bar in CI so humans do not renegotiate under launch pressure: secret scan, dependency audit, unit tests including authz negatives, preview deploy, live security scan failing on criticals. For security-headers-checker-related paths, add CODEOWNERS so reviews land on people who understand the threat model.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"

Environment separation (2)

Production credentials must not appear in previews or local agent sandboxes. Separate projects or branches for data stores, separate OAuth redirect allowlists, and separate Stripe test vs live keys. Document the matrix where coding agents can read it so ‘make preview work’ does not copy prod secrets again.

// deny-by-default sketch used near security-headers-checker
export function assertOwner(userId: string, ownerId: string) {
  if (userId !== ownerId) throw new Error('forbidden');
}

Logging, monitoring, and abuse (3)

Log authentication failures, authorization denials, and high-cost endpoints with request ids. Alert on spikes. Rate limit auth and AI proxy routes. For security-headers-checker, define what ‘abnormal’ looks like before an attacker teaches you under load.

Dependency and supply chain (4)

Lockfiles, immutable CI installs, pinned GitHub Actions, and verification of packages the model suggests. Hallucinated package names are a real path. On security-headers-checker changes that touch package manifests, require a human to open the registry page once.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"

Human process and training (5)

New engineers should break a demo app on purpose, fix it, and rescan. That training beats a PDF policy. For security-headers-checker, keep one golden path example of a secure change and one of a rejected insecure change in internal docs.

Operational checklist for security-headers-checker (6)

Treat security-headers-checker as a production surface with an owner, a review cadence, and a verification step after every AI-assisted change. Write the owner name in the repo SECURITY.md. Schedule a monthly re-read of controls that touch authentication, secrets, and data access. When an agent opens a PR against this area, require dual-user tests and a preview scan before merge. Keep a short incident appendix: which keys to rotate, which dashboards to check, who communicates with users.

// deny-by-default sketch used near security-headers-checker
export function assertOwner(userId: string, ownerId: string) {
  if (userId !== ownerId) throw new Error('forbidden');
}

Common AI-generator mistakes on security-headers-checker (7)

Generators optimize for demos: open data paths, client-trusted roles, missing rate limits, and secrets in env files that ship to browsers. On security-headers-checker, re-check those classes after every feature prompt. Search diffs for deleted middleware, new admin routes, and dependency adds. Reject ’temporarily disable auth’ comments without a tracking ticket and expiry.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"

Verification commands and proofs (8)

Proof beats intention. For security-headers-checker, keep a script or checklist that demonstrates deny paths: anonymous access fails, user A cannot read user B, webhooks reject bad signatures, and bundles lack server secrets. Store the last run date next to the checklist. If the date is older than your release cadence, you are flying blind.

CI and release gates (9)

Encode the minimum bar in CI so humans do not renegotiate under launch pressure: secret scan, dependency audit, unit tests including authz negatives, preview deploy, live security scan failing on criticals. For security-headers-checker-related paths, add CODEOWNERS so reviews land on people who understand the threat model.

Environment separation (10)

Production credentials must not appear in previews or local agent sandboxes. Separate projects or branches for data stores, separate OAuth redirect allowlists, and separate Stripe test vs live keys. Document the matrix where coding agents can read it so ‘make preview work’ does not copy prod secrets again.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"
// deny-by-default sketch used near security-headers-checker
export function assertOwner(userId: string, ownerId: string) {
  if (userId !== ownerId) throw new Error('forbidden');
}

Logging, monitoring, and abuse (11)

Log authentication failures, authorization denials, and high-cost endpoints with request ids. Alert on spikes. Rate limit auth and AI proxy routes. For security-headers-checker, define what ‘abnormal’ looks like before an attacker teaches you under load.

Dependency and supply chain (12)

Lockfiles, immutable CI installs, pinned GitHub Actions, and verification of packages the model suggests. Hallucinated package names are a real path. On security-headers-checker changes that touch package manifests, require a human to open the registry page once.

Human process and training (13)

New engineers should break a demo app on purpose, fix it, and rescan. That training beats a PDF policy. For security-headers-checker, keep one golden path example of a secure change and one of a rejected insecure change in internal docs.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"

Operational checklist for security-headers-checker (14)

Treat security-headers-checker as a production surface with an owner, a review cadence, and a verification step after every AI-assisted change. Write the owner name in the repo SECURITY.md. Schedule a monthly re-read of controls that touch authentication, secrets, and data access. When an agent opens a PR against this area, require dual-user tests and a preview scan before merge. Keep a short incident appendix: which keys to rotate, which dashboards to check, who communicates with users.

// deny-by-default sketch used near security-headers-checker
export function assertOwner(userId: string, ownerId: string) {
  if (userId !== ownerId) throw new Error('forbidden');
}

Common AI-generator mistakes on security-headers-checker (15)

Generators optimize for demos: open data paths, client-trusted roles, missing rate limits, and secrets in env files that ship to browsers. On security-headers-checker, re-check those classes after every feature prompt. Search diffs for deleted middleware, new admin routes, and dependency adds. Reject ’temporarily disable auth’ comments without a tracking ticket and expiry.

Verification commands and proofs (16)

Proof beats intention. For security-headers-checker, keep a script or checklist that demonstrates deny paths: anonymous access fails, user A cannot read user B, webhooks reject bad signatures, and bundles lack server secrets. Store the last run date next to the checklist. If the date is older than your release cadence, you are flying blind.

# smoke verification sketch for security-headers-checker
curl -s -o /dev/null -w '%{http_code}\n' "$PREVIEW_URL/healthz"

COMMON QUESTIONS

01
What headers does the tool check?
Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and CORS (Access-Control-Allow-* family). Plus legacy headers like X-XSS-Protection that should not be set.
Q&A
02
Does a perfect score mean I'm secure?
No. Headers are one layer. An app with flawless headers can still have missing auth, exposed keys, and open databases. Use this alongside the full VibeEval scan.
Q&A
03
My CSP is too strict and breaks inline scripts — what do I do?
Hash or nonce every inline script rather than allowing 'unsafe-inline'. The scanner shows which scripts need nonces and provides the hash values.
Q&A
04
Do I need both X-Frame-Options and CSP frame-ancestors?
Prefer CSP frame-ancestors for modern browsers; X-Frame-Options DENY remains useful for older clients. Setting both consistently (deny / none) is fine.
Q&A
05
Will headers fix missing Supabase RLS?
No. Headers protect browser behavior (XSS impact, clickjacking, HTTPS). Database authorization is a separate control — use the Supabase RLS Checker.
Q&A
06
How often should I re-check headers?
After every deploy config change (vercel.json, _headers, CDN, reverse proxy) and when you add third-party scripts that force CSP updates.
Q&A
07
Why do preview and production grades differ?
Preview hosts often omit custom headers, use different CDN rules, or inherit framework defaults only. Always grade both the production hostname and a representative preview URL.
Q&A
08
Is X-XSS-Protection still recommended?
No. Modern guidance is to omit it or set it to 0 and rely on CSP. Legacy XSS auditors were inconsistently implemented and can introduce side effects.
Q&A

HEADERS FIXED? SCAN THE APP

Security headers are table stakes. The agent still needs to test auth, RLS, tokens, and API behavior on the same URL.

14-day free trial · No credit card · Cancel anytime

RUN FULL SCAN