security-headers
Headers are set by the deployment, not by the code alone: the platform, CDN, and reverse proxy all matter. So this skill starts by asking whether it is looking at production.
Activate when
- A deployed URL (production or a production-like preview) exists, or the owner is about to configure hosting.
production-allroutes here (check 39).- Not on a local dev server as a verdict (it is UNKNOWN), and not as a replacement for a security review.
Inspect
-
Where are you looking? Local dev, preview, or production. Headers on
localhostsay almost nothing about a Vercel/Netlify/Cloudflare deployment. -
Fetch headers:
curl -sI -L https://<site>/and a few key pages, an API route, and a static asset. Note the redirect chain (http://→https://, apex ↔www), status, and each hop's headers. TLS certificate validity and expiry (curl -vIoutput oropenssl s_client), HTTP→HTTPS redirect, mixed content (observe-runtimereportsMIXED_CONTENT). -
Evaluate each header for this site:
Header Good baseline Notes Strict-Transport-Securitymax-age=31536000; includeSubDomains(addpreloadonly when committed)only once HTTPS is solid; on the production host Content-Security-Policytailored; starts in Content-Security-Policy-Report-Onlysee below X-Content-Type-OptionsnosniffReferrer-Policystrict-origin-when-cross-originor stricterconsider what URLs may leak Permissions-Policydisable unused features (camera, microphone, geolocation, payment) Framing frame-ancestorsin CSP (orX-Frame-Options: DENY/SAMEORIGIN)unless embedding is intended Cross-Origin-*(COOP/COEP/CORP)only where needed can break embeds; do not add blindly Cookies Secure,HttpOnly,SameSiteweb-securityInfo leaks remove X-Powered-By, verboseServerversionslow priority Caching private pages Cache-Control: private/no-storeauthenticated pages must not be publicly cacheable -
CSP from evidence, not from a template. Get the real origins from
observe-runtime(vendors and third-party hosts per page, with--stepscovering key flows: login, checkout, embeds, consent) and source (inline scripts,eval, web workers, WebSockets, frames, fonts, images, forms, service workers). Build directives:default-src 'self';script-srcwith nonces/hashes for inline scripts (avoid'unsafe-inline'and'unsafe-eval'where the framework supports it; Next.js supports nonces; some analytics/tag managers require allowances);connect-srcfor APIs/analytics/websockets;img-src,font-src,style-src,frame-src,form-action,base-uri 'self',object-src 'none',frame-ancestors. Ship as Report-Only first, collect violations against real usage (report-to/report-uri), and tighten. Flag anything that forces'unsafe-inline'/'unsafe-eval'and why. -
Mismatch between consent and CSP: a CSP that allows vendors the consent design says are gated is fine; one that blocks a needed vendor breaks the site: test both states.
Evidence that counts
Label each claim OBSERVED, SOURCE-INDICATED, DECLARED, INFERRED, UNKNOWN, or REVIEW REQUIRED. UNKNOWN is never a pass and never a failure.
- Headers from the deployed URL: OBSERVED. Headers configured in
next.config.js/vercel.json/_headers: SOURCE-INDICATED (platform may override). - A CSP is verified only by running the real flows under it and seeing no violations, not by reading it.
- "No CSP" is a finding on a deployed site handling accounts or data, but low-risk marketing sites may reasonably start with a Report-Only policy.
May change
Add headers via the project's mechanism (next.config.js headers(), vercel.json, netlify.toml/_headers, server middleware, Cloudflare rules); add HSTS after confirming HTTPS works on the production host; start CSP as Report-Only with the observed origin list; remove leaky headers. Do not enforce a CSP that has not been run against real flows; do not add preload; do not disable a working third-party integration to make a policy stricter.
Must not claim
"Secure headers", "A+ on securityheaders.com", "protected against XSS/clickjacking", or that a policy is enforced when it is Report-Only.
Verify
Fetch headers again on the deployed URL after each change; run the key user flows under the policy in a browser with the console open (no CSP violations); confirm no third-party feature broke (embeds, payments, consent, analytics as designed); confirm redirect chain and HSTS.
Escalate
A CSP needing unsafe-inline/unsafe-eval for critical scripts (owner and security review); payment or auth pages where a bad CSP would block checkout; multi-subdomain HSTS/preload decisions; anything requiring platform-level access you do not have.
No change is valid when
Only a local build exists (mark UNKNOWN and prepare the config), or deployed headers already match the site's needs. Do not add a strict CSP to a site you cannot test.
