Tells browsers to always connect over HTTPS. Graded on max-age (≥ 31536000), `includeSubDomains`, and `preload` (the strictest configuration enrolls the domain in the HSTS preload list, baked into Chrome, Firefox, and Safari shipping).
Free HTTP security headers checker — HSTS preload eligibility, CSP directive parser, cookie audit, real socket timings, copy-paste fixes.
HTTP Security Headers Analyzer fetches your site over HTTPS with native sockets — capturing real DNS, TCP, TLS handshake, first-byte, and download timings — then parses every response header and grades it against a modern baseline. Checks HSTS preload eligibility (max-age, includeSubDomains, preload directive), parses CSP into directives and flags `unsafe-inline` / wildcards / missing `object-src`, audits every `Set-Cookie` for Secure / HttpOnly / SameSite / prefix rules, warns on deprecated headers (X-XSS-Protection, HPKP, Expect-CT), and generates ready-to-paste nginx / Apache / Express snippets for every header you're missing. Single A–F grade you can share with a PM.
What gets graded
Twelve security headers, graded against modern best-practice values — not just 'present or absent' but 'configured correctly'. The grade is composite; the per-header breakdown tells you exactly what to fix.
Whitelists which sources scripts, styles, frames, and other resources may load from. The strongest single defense against XSS. Graded on `default-src` strictness, presence of `'unsafe-inline'` / `'unsafe-eval'` (both significantly weaken protection), nonce/hash usage, and `frame-ancestors`.
Prevents your site from being embedded in iframes on other origins (UI redress / clickjacking attacks). `DENY` or `SAMEORIGIN` accepted. Modern best practice: replace with CSP `frame-ancestors` which is more granular.
`nosniff` prevents browsers from second-guessing the declared content-type. Without it, a file labeled `text/plain` containing HTML could be executed as a script under some conditions. Cheap to set; should always be present.
Controls how much Referer information is sent to third-party origins. Best practice: `strict-origin-when-cross-origin` or `no-referrer`. Mitigates leak of sensitive path/query data in outbound requests.
Restricts which browser features (geolocation, camera, microphone, payment, USB, etc) can be used by your origin and embedded iframes. Hardening reduces attack surface from compromised third-party scripts.
`same-origin` enables cross-origin isolation, which unlocks high-resolution timers and shared memory and gives stronger protection against Spectre-class attacks. Increasingly important for security-sensitive sites.
Pairs with COOP. `require-corp` means cross-origin resources must opt in via CORP or CORS. The combination of COOP + COEP is what enables `crossOriginIsolated` in modern browsers.
Every `Set-Cookie` is parsed individually. Each cookie is checked for `Secure`, `HttpOnly`, `SameSite`, and the `__Host-` / `__Secure-` prefix rules (`__Host-` requires Secure, no Domain, Path=/). Missing flags surface as per-cookie issues rather than a blanket 'insecure' label.
Native `http`/`https` request with socket-timing hooks — captures DNS lookup, TCP connect, TLS handshake, first-byte, and download time separately. Also reports `content-encoding`, actual compression ratio (decompresses gzip/deflate/br to compare), total size, and cache status parsed from `cf-cache-status` / `x-cache` / `age`.
Every redirect hop is captured — URL, status code, time — up to five hops. Detects HTTP→HTTPS enforcement (if the initial `http://` request lands on `https://` at the end), redirect loops, and unnecessary chains that inflate TTFB.
Flags `X-XSS-Protection` (ignored by modern browsers), `Public-Key-Pins` (removed from Chromium), `Expect-CT` (deprecated as of Chrome 107), and legacy `Feature-Policy`. Also surfaces `Server` and `X-Powered-By` as fingerprinting leaks — one config line hides them.
Why security headers are the cheapest hardening you can do
Most security headers are one-line config changes that immediately raise the cost of common attacks. Five reasons they're underrated:
- HSTS eliminates the SSL-stripping attack class entirely. Without HSTS, an attacker on the network can intercept a user's first HTTP-to-HTTPS upgrade and serve the site over plain HTTP, capturing credentials in transit. With HSTS (and preload), browsers refuse plain HTTP for your domain from day one. The cost: setting one header. The benefit: a whole attack class gone.
- CSP is the single best XSS defense — when configured well. A strict CSP that disallows `'unsafe-inline'` scripts means an attacker who finds an XSS injection still can't execute it. Most real-world CSP deployments include `'unsafe-inline'` because the codebase relies on inline scripts — partially defeating CSP's purpose. The grader surfaces this tradeoff explicitly.
- X-Frame-Options stops clickjacking outright. Without `X-Frame-Options` or CSP `frame-ancestors`, your authenticated user could be tricked into clicking 'Confirm transfer' on your site by embedding it in an attacker's iframe with a transparent overlay. One header line stops it.
- Modern compliance frameworks expect these headers. PCI-DSS, SOC 2, ISO 27001, HIPAA — all reference HTTP security headers in their hardening guidance. An A or A+ grade on this checker maps directly to satisfying common compliance checklists. F-grade sites get findings.
- The Mozilla Observatory bar moved up. Five years ago, an A meant 'has HSTS and X-Frame-Options'. Today it means 'CSP with no `'unsafe-inline'`, COOP + COEP for cross-origin isolation, Permissions-Policy locking down features'. The bar keeps rising; sites that haven't updated their headers in years grade lower than they used to.
Native-socket fetch, parse everything, grade against a modern baseline
A header check is one HTTPS request, five parsers, and a scored report. We use Node's native `http`/`https` (not a wrapper) so we can hook the socket lifecycle and report the real time each phase took.
- Stage 1 — Fetch with instrumented sockets Send a real HTTPS GET request with `Accept-Encoding: gzip, deflate, br`. Register listeners on `socket.lookup` (DNS), `socket.connect` (TCP), `socket.secureConnect` (TLS handshake). Record timestamp on the first `response` event (TTFB) and `end` event (download). Follow up to 5 redirects, timing each hop. Fall back to plain HTTP only on hard TLS failures — surface the fallback in the report.
- Stage 2 — Parse each security header HSTS into `max-age`, `includeSubDomains`, `preload` — plus preload-eligibility check against Chromium's rules (`max-age ≥ 31536000` AND both flags). CSP into directives and source lists — flag `'unsafe-inline'`, `'unsafe-eval'`, wildcards, missing `object-src` / `base-uri` / `frame-ancestors`, report-only mode. Each `Set-Cookie` into `Secure` / `HttpOnly` / `SameSite` / `__Host-` / `__Secure-` prefix, with per-cookie issue list.
- Stage 3 — Detect cache and compression Cache status inferred from `cf-cache-status`, `x-cache`, `cache-status`, or a non-zero `age` header. Compression ratio computed by decompressing the actual body (gzip / deflate / br via `zlib`) and comparing sizes — not just 'is content-encoding present'.
- Stage 4 — Flag deprecated / fingerprinting headers `X-XSS-Protection` (deprecated), `Public-Key-Pins` (removed from Chromium), `Expect-CT` (deprecated), `Feature-Policy` (renamed to Permissions-Policy). `Server` and `X-Powered-By` are surfaced as fingerprinting leaks.
- Stage 5 — Score and grade Start at 100. Deduct 20 for missing HSTS, 25 for missing CSP, 10 each for missing X-Content-Type-Options / X-Frame-Options / HTTPS enforcement. Per CSP finding: -10 (high), -5 (medium), -2 (low). Per cookie with issues: -5 max. Deprecated headers: -1 to -3. Grade: A (≥90), B (≥80), C (≥70), D (≥60), F (<60). Preload-eligible HSTS avoids the -5 preload penalty.
- Stage 6 — Build copy-paste snippets For every missing header, generate a ready-to-drop config for nginx (`add_header ... always;`), Apache (`Header always set ...`), and Express.js (`res.setHeader(...)`). Values follow current best practice — `default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests` for CSP, `strict-origin-when-cross-origin` for Referrer-Policy, hard-deny list for Permissions-Policy.
- Stage 7 — Cross-check related headers Some headers interact. CSP `frame-ancestors` supersedes X-Frame-Options — if both set, X-Frame-Options gets an 'redundant' info flag. `SameSite=None` without `Secure` gets a per-cookie error. `__Host-` prefix without Secure/no-Domain/Path=/ gets flagged. The grader surfaces these compound findings.
Why Content-Security-Policy is hard to get right
CSP is the most powerful security header — and the hardest to configure. Done well, it eliminates XSS. Done poorly, it provides false security or breaks the site. Five things to know:
- `default-src 'self'` is the safe foundation Every CSP should start with `default-src 'self'` (or stricter) and then explicitly opt-in for each resource type that needs broader access. Without `default-src`, missing directives fall back to permissive defaults — defeating the purpose.
- `'unsafe-inline'` is the silent CSP killer Allowing `'unsafe-inline'` for scripts (or styles) means inline `<script>` tags and `onclick=` handlers run — which means an XSS injection's payload runs. Most legacy sites need `'unsafe-inline'` because their codebase relies on inline event handlers. Real CSP protection requires refactoring or nonce/hash usage.
- Nonces and hashes are the modern path Generate a fresh random nonce per response, include it in CSP (`script-src 'nonce-randomvalue'`), and tag every inline script with the matching `nonce` attribute. Injected scripts (without the nonce) are blocked; legitimate inline scripts (with the nonce) run. Hash-based approaches do the same but with content hashes.
- `frame-ancestors` supersedes X-Frame-Options The CSP `frame-ancestors` directive does the same job as X-Frame-Options but more flexibly (multiple origins, full URL matching). Setting `frame-ancestors 'self'` is functionally equivalent to `X-Frame-Options: SAMEORIGIN`. Set both for backward compatibility with older browsers.
- Report-Only mode is your friend during rollout Deploy CSP via the `Content-Security-Policy-Report-Only` header first. Violations are reported to your `report-uri` endpoint but don't block anything. Run for a few weeks, fix every violation you see, then switch to the enforcing `Content-Security-Policy` header.
- Strict-dynamic and trusted types are the cutting edge `'strict-dynamic'` allows scripts loaded by an already-trusted (nonce'd) script. `require-trusted-types-for 'script'` enables Trusted Types, which blocks unsafe DOM sinks at the engine level. Both are modern hardening; few sites have adopted them yet.
Real socket timings, not a stopwatch on the wrapper
Most 'header check' tools return one number — total request time. That hides the interesting question: where did the ms go? Was it DNS? The TLS handshake? The server itself? We wire listeners onto the socket lifecycle so every phase is measured separately.
- DNS Lookup Time from request start to socket `lookup` event — the domain-to-IP resolution. High values (>50ms) mean slow recursive resolver or no ISP-side caching. Anycast providers (Cloudflare, Google) usually land under 30ms.
- TCP Connect Time from DNS complete to socket `connect` event — the three-way handshake. Determined by network RTT to the origin. Under 30ms for same-continent origins is typical; over 200ms usually means the request is crossing an ocean.
- TLS Handshake Time from TCP connect to socket `secureConnect` — cipher negotiation + certificate exchange. Under 60ms with TLS 1.3 + session resumption. Over 200ms suggests TLS 1.2 with full handshake, weak cipher choices, or a large certificate chain.
- First Byte (TTFB) Time from request start to the first byte of the HTTP response. Includes DNS + TCP + TLS + server processing. Under 200ms is 'A' territory; over 1 second suggests slow backend, cold cache, or no CDN.
- Download Time from TTFB to response end. Dominated by response size and available bandwidth. If TTFB is fast but download is slow, the payload is too large or compression is off.
- Compression ratio (real, not assumed) We decompress the actual response body (gzip / deflate / br via `zlib`) and compare against the wire size. Ratios below 0.3 (>70% saving) are ideal for HTML. Ratios of 1.0 mean nothing was compressed — check `content-encoding` and enable Brotli.
- Cache status Parsed from CDN-specific headers — `cf-cache-status` (Cloudflare), `x-cache` (Fastly / AWS CloudFront), the RFC-standard `cache-status`, or `age` > 0 as a fallback signal that the response is served from cache.
- Redirect chain Every hop up to five redirects — URL, status code, per-hop duration. Detects HTTP→HTTPS enforcement, redirect loops, and unnecessary chains. Each hop adds a full DNS + TCP + TLS round-trip in the worst case.
Copy-paste snippets for every missing header — nginx, Apache, Express
The gap between 'I know I need this header' and 'I've deployed it' is usually five minutes of googling the right config syntax. We generate it for you — modern best-practice values, ready to drop into your server config or middleware.
# Drop into your server{} block. `always` ensures headers ship on error responses too.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()" always;
# Suppress fingerprinting
server_tokens off;
more_clear_headers 'Server' 'X-Powered-By';# Requires mod_headers. Drop into your VirtualHost or .htaccess.
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()"
# Suppress fingerprinting
ServerTokens Prod
Header unset Server
Header unset X-Powered-By// Option A: helmet (most Express apps)
import helmet from 'helmet';
app.use(helmet({
strictTransportSecurity: {maxAge: 31536000, includeSubDomains: true, preload: true},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
},
referrerPolicy: {policy: 'strict-origin-when-cross-origin'},
permittedCrossDomainPolicies: false,
}));
app.disable('x-powered-by');
// Option B: hand-rolled middleware
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
res.setHeader('Content-Security-Policy', "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests");
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()');
next();
});
app.disable('x-powered-by');Use this programmatically
Every header, per-header grade, and recommendation is available as JSON. Useful for CI gates that prevent regressing security headers, vendor due diligence, and continuous header-monitoring across a portfolio of sites.
curl 'https://api.domainscan.in/api/v1/security/headers?domain=github.com'const res = await fetch(
'https://api.domainscan.in/api/v1/security/headers?domain=github.com'
);
const {data} = await res.json();
console.log(data.grade); // 'A'
console.log(data.score); // 90
console.log(data.duration, 'ms total'); // 235
// Performance breakdown
const p = data.performance;
console.log(`DNS ${p.dnsLookupTime}ms · TCP ${p.tcpConnectTime}ms · TLS ${p.tlsHandshakeTime}ms · TTFB ${p.firstByteTime}ms`);
console.log(`Encoding: ${p.encoding} · Compression: ${(p.compressionRatio * 100).toFixed(0)}%`);
// HSTS preload check
if (data.analysis.hsts.preloadEligible) {
console.log('HSTS preload-ready — submit at hstspreload.org');
}
// CSP findings
data.analysis.csp.findings.forEach(f => {
console.log(`[${f.severity}] CSP: ${f.text}`);
});
// Cookie audit
data.analysis.cookies.cookies.forEach(c => {
if (c.issues.length) console.log(`Cookie ${c.name}: ${c.issues.join('; ')}`);
});
// CI gate: fail build if grade regresses
if (['D', 'F'].includes(data.grade)) {
console.error('Security headers grade regressed:', data.grade);
process.exit(1);
}
// Copy-paste snippets for missing headers
Object.entries(data.snippets).forEach(([header, tpl]) => {
console.log(`\n# ${header} — nginx`);
console.log(tpl.nginx);
});{
"success": true,
"url": "https://github.com",
"status": 200,
"protocol": "https",
"duration": 235,
"score": 90,
"grade": "A",
"performance": {
"dnsLookupTime": 47,
"tcpConnectTime": 28,
"tlsHandshakeTime": 53,
"firstByteTime": 161,
"downloadTime": 72,
"totalSize": 126333,
"uncompressedSize": 577068,
"compressionRatio": 0.219,
"encoding": "gzip",
"cacheStatus": "hit",
"redirectCount": 0,
"redirectChain": [{"url": "https://github.com", "status": 200, "duration": 235}]
},
"analysis": {
"hsts": {
"present": true,
"maxAge": 31536000, "maxAgeDays": 365,
"includeSubDomains": true, "preload": true,
"preloadEligible": true
},
"csp": {
"present": true, "reportOnly": false, "directiveCount": 8,
"findings": [
{"severity": "low", "text": "style-src allows 'unsafe-inline'"},
{"severity": "info", "text": "upgrade-insecure-requests enabled"}
]
},
"cookies": {
"present": true, "count": 3,
"cookies": [
{"name": "_gh_sess", "secure": true, "httpOnly": true, "sameSite": "lax", "issues": []},
{"name": "_octo", "secure": true, "httpOnly": false, "sameSite": "lax",
"issues": ["Missing HttpOnly — accessible to JavaScript (XSS risk)"]}
]
},
"redirects": {"enforcesHttps": true, "httpRedirectsToHttps": false, "finalUrl": "https://github.com"},
"deprecated": [
{"header": "X-XSS-Protection", "severity": "low", "message": "Deprecated. Modern browsers ignore it. Rely on CSP."}
]
},
"snippets": {
"Permissions-Policy": {
"value": "accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()",
"nginx": "add_header Permissions-Policy \"...\" always;",
"apache": "Header always set Permissions-Policy \"...\"",
"express": "res.setHeader('Permissions-Policy', '...');"
}
},
"headers": {"strict-transport-security": "...", "content-security-policy": "..."}
}How teams use the Security Headers Checker
Six patterns we see most often:
Wire the API into your CI/CD pipeline. Block deploys that regress the security-headers grade. Catches the well-intentioned middleware change that accidentally removed HSTS three releases ago.
PCI-DSS, SOC 2, ISO 27001, HIPAA audits all reference HTTP security headers. A documented A or A+ grade satisfies the relevant controls without manual evidence-gathering.
Evaluating a SaaS that will handle your customer data. Their security-headers grade is a leading indicator of broader security maturity. F-grade sites correlate with deeper issues.
Before paying for an external pen test, run security-headers and other low-hanging-fruit checks. Fix the obvious findings yourself; let the pen-testers spend their hours on harder issues.
Run the checker against every public site of a target acquisition. The header grades tell you how much post-acquisition hardening work to expect.
Cron the API against your portfolio of sites. Alert on grade regressions. Catches the silent drift when a new CDN config or middleware change accidentally removes a critical header.
Common questions
- What are HTTP security headers? HTTP response headers that the server sends to instruct the browser about security policies. They control things like 'always use HTTPS' (HSTS), 'don't allow this site in iframes' (X-Frame-Options), 'only allow scripts from these sources' (CSP), and 'don't send Referer to third parties' (Referrer-Policy). Each header is a one-line server config and immediately raises the cost of common attacks.
- Which security headers are most important? Top priorities, in order: Strict-Transport-Security (eliminates SSL-stripping), Content-Security-Policy (defends against XSS), X-Frame-Options or `frame-ancestors` (defends against clickjacking), X-Content-Type-Options (defends against MIME-sniffing). These four cover the biggest attack classes. The remaining headers (Referrer-Policy, Permissions-Policy, COOP, COEP) add depth-of-defense.
- What is HSTS and should I enable preload? Strict-Transport-Security tells browsers to always connect to your domain over HTTPS, never plain HTTP. `preload` enrolls your domain in the HSTS preload list shipped with Chrome, Firefox, and Safari — so even a first-time visitor never sends plain HTTP. Preload is excellent for security but hard to reverse — once you're on the list, removal takes months. Submit via hstspreload.org only when you're confident HTTPS is the only protocol you'll ever serve.
- What is Content-Security-Policy and is `'unsafe-inline'` bad? CSP whitelists which sources can load scripts, styles, frames, etc. `'unsafe-inline'` allows inline `<script>` tags and event handlers — which means an XSS injection's payload runs. It's not 'bad' in the sense of immediately broken, but it significantly weakens CSP's XSS protection. Modern approach: use nonces or hashes for the inline scripts you actually need, remove `'unsafe-inline'`.
- Should I use X-Frame-Options or `frame-ancestors`? Both, for backward compatibility. CSP `frame-ancestors 'self'` is the modern, more flexible directive — supports multiple origins and full URL matching. X-Frame-Options (`DENY` or `SAMEORIGIN`) is the legacy header understood by older browsers. The grader treats `frame-ancestors` as the primary signal; X-Frame-Options as the fallback.
- What is COOP / COEP and do I need them? Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy work together to enable cross-origin isolation — required for high-resolution timers, SharedArrayBuffer, and stronger Spectre-class defenses. `COOP: same-origin` + `COEP: require-corp` is the strict pairing. Required if you use SharedArrayBuffer or want the strongest process isolation; otherwise nice-to-have.
- What is Permissions-Policy? Restricts which browser features (geolocation, camera, microphone, payment APIs, USB, accelerometer, etc) the page and embedded iframes can use. Modern replacement for Feature-Policy. Best practice: explicitly deny features you don't use — `Permissions-Policy: geolocation=(), camera=(), microphone=()`. Reduces attack surface from compromised third-party scripts.
- Why is my site graded lower than expected? Common causes: HSTS max-age too short (under 1 year), no CSP at all, CSP uses `'unsafe-inline'`, X-Frame-Options missing while no `frame-ancestors` directive present, Server header leaking detailed version info, missing Permissions-Policy, cookies missing Secure / HttpOnly / SameSite. The per-header breakdown identifies the exact gap and the recommended fix value.
- How is HSTS preload eligibility checked? Chromium's preload rules require three things simultaneously: `max-age ≥ 31536000` (1 year), `includeSubDomains` directive, and the literal `preload` token. We parse each individually and show three checkmarks (or X marks) with the actual `max-age` in days. Green across the board means you can submit at hstspreload.org — but understand submission is near-permanent, so only submit when HTTPS is truly the only protocol you'll ever serve.
- What does the cookie audit check? Every `Set-Cookie` header is parsed individually. Per cookie we check: `Secure` (blocks HTTP transit), `HttpOnly` (blocks JavaScript access — XSS defence), `SameSite=Lax|Strict|None` (CSRF defence). `SameSite=None` without `Secure` is flagged as an error. Cookies prefixed `__Host-` must have Secure, no Domain attribute, and Path=/ (browser refuses to set otherwise). `__Secure-` prefix requires Secure. Each cookie is shown in a table with pass/fail icons and an issue list.
- What performance metrics do you capture? Using native `http`/`https` with socket lifecycle hooks: DNS lookup, TCP connect, TLS handshake, first-byte (TTFB), download, total size, uncompressed size, compression ratio (from actually decompressing the body), content-encoding, cache status (parsed from cf-cache-status / x-cache / age), and the full redirect chain with per-hop timings. Not synthesised — measured on the real request.
- Do you show copy-paste fixes for missing headers? Yes — for every missing header we generate three snippets: nginx (`add_header ... always;`), Apache (`Header always set ...`), Express.js (`res.setHeader(...)`). Values follow current best practice. Tabbed switcher, one-click copy, ready to paste into your config file or middleware.
- Which deprecated headers do you flag? `X-XSS-Protection` (modern browsers ignore it), `Public-Key-Pins` and `Public-Key-Pins-Report-Only` (removed from Chromium — use CT monitoring + CAA instead), `Expect-CT` (deprecated as of Chrome 107 — CT is enforced by default), `Feature-Policy` (renamed to Permissions-Policy). We also flag `Server` and `X-Powered-By` as fingerprinting leaks — one config line hides them.
- How does the A–F grade work? Start at 100. Deduct 20 for missing HSTS (or 5 if HSTS present but not preload-eligible), 25 for missing CSP, 10 each for missing X-Content-Type-Options / X-Frame-Options (unless CSP frame-ancestors is set) / HTTPS enforcement, 5 each for missing Referrer-Policy / Permissions-Policy. Per CSP finding: -10 high, -5 medium, -2 low. Per cookie with issues: up to -5. Deprecated headers: -1 to -3. Grade: A (≥90), B (≥80), C (≥70), D (≥60), F (<60).