How we score
HeaderLab's grade is based on what HTTP security headers your site returns and how they're configured. This page explains the scoring model behind that grade — what we check, how we weight it, and where we deliberately stop.
Our scoring philosophy
A scanner can take two paths. It can check whether a header is present and call that good enough, or it can look at what the header actually says and grade accordingly. We do both.
Presence is the floor, not the ceiling. A site that sends Strict-Transport-Security: max-age=0 technically has HSTS — but the header is disabling itself. A Content-Security-Policy that allows 'unsafe-inline' and * is present, but it isn't really protecting much of anything.
So our scoring reflects two questions: is the header there, and is the configuration strong? We weight headers by the real-world impact of getting them wrong, reward configurations that match current best practice, and give partial credit where it makes sense — a CSP that blocks most attack vectors is meaningfully better than none, even if it isn't perfect.
The scoring model
Every scan starts at zero and earns up to a raw total, which is then normalized to a 0–100 final score. Each of the six headers we evaluate carries a weight based on its severity tier.
Headers are weighted as follows:
- Content-Security-Policy — critical (raw weight 28)
- Strict-Transport-Security — critical (raw weight 28)
- X-Frame-Options / frame-ancestors — high (raw weight 20)
- Referrer-Policy — medium (raw weight 12)
- X-Content-Type-Options — medium (raw weight 12)
- Permissions-Policy — medium (raw weight 12)
Total raw weight across all headers is 112; your earned points are scaled to a 0–100 score for the final grade. We display raw weights here rather than rounded percentages because the underlying scoring logic uses raw weights directly, and we'd rather show what the analyzer actually does than a tidier number that doesn't match.
Within each header, presence earns a baseline portion of the weight. The rest is unlocked by configuration quality: a long HSTS max-age with preload earns more than a short max-age without it; a CSP using nonces and strict-dynamic earns more than one relying on broad allowlists.
Partial implementations get partial credit. A header that's present but weakly configured will still score above missing entirely — but well below a strict, modern configuration. When a header is configured in a way that looks protective but isn't (HSTS with max-age=0, CSP with 'unsafe-inline' on script-src), we treat that as a soft penalty rather than a free pass.
Header-by-header breakdown
For each header we evaluate, this section explains what's checked, where things commonly go wrong, and what's actually being prevented when the header is set well.
Content-Security-Policy — Critical severity
What we check. The presence of a policy, then the strength of its directives — particularly script-src, object-src, base-uri, and frame-ancestors. We reward modern patterns (nonces, 'strict-dynamic', hashes) and penalize broad allowlists, wildcards, and unsafe keywords. The full set of weakness checks runs through our CSP Evaluator and is summarized in the result.
Common pitfalls. 'unsafe-inline' on script-src (the single most damaging mistake), 'unsafe-eval' in production, wildcards like * or https: in script sources, missing object-src 'none' or base-uri, and CDNs in the allowlist that themselves host arbitrary user content.
Why this matters. Cross-site scripting remains one of the most exploited web vulnerabilities, and CSP is the strongest browser-enforced defense against it. A strict CSP turns an XSS bug from a full account takeover into a refusal by the browser to run the injected script. A loose CSP is closer to no CSP at all.
Strict-Transport-Security — Critical severity
What we check. The presence of max-age, its duration, whether includeSubDomains is set, and whether preload is declared. Longer durations and broader scopes earn more credit.
Common pitfalls. A max-age of zero (which disables HSTS), durations measured in days rather than months, and omitting includeSubDomains on sites that serve subdomain content. Preload is optional, but its absence caps the score below the maximum.
Why this matters. Without HSTS, a single insecure request — a typed URL, an old bookmark, a stripped link — gives an on-path attacker a chance to intercept the connection before it upgrades to HTTPS. HSTS tells the browser to never try plain HTTP again; preload closes the first-visit gap by baking that policy into the browser itself.
X-Frame-Options / frame-ancestors — High severity
What we check. Either X-Frame-Options or the CSP frame-ancestors directive. We treat frame-ancestors as the modern equivalent and score them together rather than penalizing sites that chose one over the other. The strongest values are DENY / 'none' and SAMEORIGIN / 'self'.
Common pitfalls. Setting neither, using the deprecated ALLOW-FROM directive (which most browsers no longer honor), or declaring a strict XFO while the CSP frame-ancestors is more permissive. When both are present and they disagree, modern browsers obey frame-ancestors — so the weaker policy wins.
Why this matters. Without framing protection, an attacker can load your site invisibly inside their own page and trick a logged-in user into clicking buttons they can't see — confirming a transfer, changing a password, granting permissions. Clickjacking is mitigated almost entirely at the header level, which makes leaving it off an unforced error.
Referrer-Policy — Medium severity
What we check. The presence of the header and the strength of its value. strict-origin-when-cross-origin earns near-full credit; no-referrer earns full credit for sites that don't need cross-origin referrer data. Comma-separated fallback values are accepted per the spec.
Common pitfalls. Setting unsafe-url (which leaks the full URL even over HTTPS), relying on the legacy no-referrer-when-downgrade default, or omitting the header on sites that handle tokens, search queries, or anything sensitive in URL paths.
Why this matters. Every outbound link, image, and script your site loads can send your current URL to the destination as the Referer. For password reset links, search-result URLs containing user queries, or pages with session tokens in the query string, that's a quiet data leak to every third-party domain you embed.
X-Content-Type-Options — Medium severity
What we check. The presence of the header with the value nosniff. This is a simple binary check — the header has one valid value.
Common pitfalls. Omitting the header entirely is the only meaningful failure.
Why this matters. Without nosniff, browsers may try to guess the content type of a response — and that guess can turn an uploaded text file into executable JavaScript, or a JSON endpoint into something the browser treats as HTML. The header has no configuration to get wrong and shuts down an entire class of MIME-confusion attacks.
Permissions-Policy — Medium severity
What we check. The presence of a Permissions-Policy header. Our current check is pass/fail on presence; the recommendation we surface in your result encourages comprehensive directive coverage — denying camera, microphone, geolocation, payment, USB, sensors, and similar features your site doesn't use.
Common pitfalls. Omitting the header entirely is by far the most common. An empty Permissions-Policy: header is a no-op, and a legacy Feature-Policy header without a modern equivalent leaves the same gap. Setting only a handful of directives leaves the rest at default-allow, which is the wrong direction for defense-in-depth.
Why this matters. A compromised third-party script — an ad, an analytics SDK, a tampered CDN library — runs in your origin and inherits its capabilities. Permissions-Policy lets you draw a hard line: this page doesn't need the camera, the microphone, the user's location, or the payment API, and the browser will refuse those calls even if the page asks. As third-party supply-chain compromises have become more common, this kind of browser-enforced ceiling on what code can do has shifted from nice-to-have to a meaningful layer of defense.
CSP analysis approach
Because Content-Security-Policy carries the largest weight and the most complexity, it gets its own evaluation pass. Our CSP Evaluator parses the policy directive by directive and runs more than fifteen weakness checks against it, then ranks the findings by severity.
Severity tiers
- Critical — issues that effectively neutralize the policy.
'unsafe-inline'onscript-srcwithout a nonce or hash fallback,*as a script source, or'unsafe-eval'in production. - High — issues that significantly weaken protection. Wildcard subdomains, scheme-only sources like
https:, missingobject-src 'none', missingbase-uri. - Medium — issues that reduce defense-in-depth. Allowlisted CDNs known to host user content, overly broad
default-src, noframe-ancestorsdirective. - Low — issues that are best-practice gaps rather than active risks. Missing
report-uri/report-to, deprecated directives, redundant declarations.
Positive signals
The evaluator also looks for modern patterns and rewards them. A policy that uses 'strict-dynamic' with a nonce sidesteps the allowlist problem entirely. Nonce-based or hash-based script sourcing earns credit even when an allowlist is present, because the nonce gates execution regardless of host. CSP Level 3 features (Trusted Types, require-trusted-types-for) push the score toward the top of the range.
The same evaluator powers our standalone CSP Evaluator tool, so you can paste any policy and see the full breakdown — not just the score, but every finding behind it.
Grade scale
The final score is mapped to a letter grade. The boundaries are deliberately wider at the top — an A+ should mean something rare, and the gap between an A and an A+ should be meaningful.
| Grade | Score | What it means |
|---|---|---|
| A+ | 95–100 | Strict, modern, minimal-attack-surface configuration. |
| A | 85–94 | Strong baseline with minor gaps. |
| B | 75–84 | Solid foundation; one or two important headers weak or missing. |
| C | 60–74 | Partial protection; several gaps a determined attacker could chain. |
| D | 40–59 | Significant exposure across multiple headers. |
| F | 0–39 | Little to no header-based protection. |
A passing grade isn't the goal — a hardened production site should aim for A or A+. Most well-known sites cluster around B, which reflects how much room there still is across the web for header-based defense.
Where our scores may differ from other scanners
Header scanners don't agree, and that's worth being upfront about. Run any production site through three different tools and you'll likely get three different grades. Here's where our scoring tends to land differently than others.
We grade configuration, not just presence. A site can earn full marks for "has CSP" on a presence-only scanner while sending a policy that allows 'unsafe-inline' on scripts. Our score will be noticeably lower in that case, because the policy isn't actually closing the gap CSP exists to close.
We're stricter on CSP weaknesses. Our evaluator runs more than fifteen checks against the policy itself, and findings ranked critical or high will cap the CSP contribution well below its maximum even when the rest of the headers are pristine. On scanners that don't analyze CSP content, the same policy can earn full CSP credit.
We weight Permissions-Policy as medium severity. Some scanners treat Permissions-Policy as optional or bonus credit. We include it in the base score at medium severity — on par with X-Content-Type-Options and Referrer-Policy — because the directive matters more as third-party script supply chains grow and browser support has matured. A site that explicitly disables camera, microphone, geolocation, payment, USB, and sensor APIs is meaningfully harder to abuse via a compromised third-party script than one that leaves all features at default-allow.
We treat XFO and frame-ancestors as equivalent. Sites that have moved their framing policy into CSP shouldn't be penalized for dropping the legacy X-Frame-Options header. Some scanners still check for both independently.
Where our scores are lower, it usually reflects a stricter read of configuration. Where they're higher, it usually reflects credit for modern patterns that older rubrics don't recognize.
What we don't check
HeaderLab focuses on response headers. There's a wider security surface that headers don't cover, and we'd rather be clear about that than imply our grade is the whole picture.
- TLS / SSL configuration. Cipher suites, protocol versions, certificate chain quality. Qualys SSL Labs is the canonical tool here.
- DNS and network controls. DNSSEC, CAA records, email authentication (SPF, DKIM, DMARC). These need DNS-level tooling.
- Application-layer vulnerabilities. XSS, SQL injection, IDOR, business-logic flaws. Headers can mitigate some of these — a well-configured CSP catches a class of XSS — but they aren't a substitute for secure code.
- Cookie security flags.
Secure,HttpOnly,SameSite. On our roadmap; not yet scored.
An A+ here means your headers are in good shape. It doesn't mean your site is secure.
Open methodology & feedback
The scoring logic isn't a black box. Everything described above lives in two files in the repository:
src/lib/headers.ts— the per-header analyzer, including weights, presence checks, and configuration scoring.src/lib/csp-evaluator.ts— the CSP weakness checks and severity rankings.
If you disagree with a weight, think a check is missing, or want to argue for a different cutoff on the grade scale, the GitHub issue tracker is the right place. We'd rather have the argument in public than calibrate in private.