Your website got an A in one security scanner and an F in another. Now what?
Before changing your server configuration, check whether both scanners actually inspected the same response.
A security-header grade compresses several decisions into one letter:
- Which URL did the scanner reach?
- What response did the server return?
- Which headers did it recognize?
- How did it interpret their values?
- How much weight did each check receive?
Change any of those, and the grade can change.
Here are five things worth checking before chasing an A.
1. You might be grading a bot challenge
Your browser loads the website successfully. A scanner receives:
HTTP/1.1 403 Forbidden
Content-Type: text/html
That response might be a firewall challenge, not the application page you expected.
Its headers still matter, but they don’t necessarily describe the page your visitors receive.
Start by comparing the final URL and HTTP status in both reports. Also check redirects: the headers on an HTTP-to-HTTPS redirect can differ from those on the destination.
For a website you own, you can inspect the response chain using a GET request:
Windows:
curl.exe -sS -L --max-redirs 10 -D - -o NUL https://example.com/
macOS/Linux:
curl -sS -L --max-redirs 10 -D - -o /dev/null https://example.com/
Replace https://example.com/ with your address. Read the final response block as well as the redirects.
This deliberately uses GET. A server can handle HEAD requests differently.
2. Finding unsafe-inline isn’t enough
Consider this policy:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
The inline exception belongs to styles. It does not grant permission to execute inline JavaScript.
Now compare:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Here, the exception appears in the script directive.
A check that searches the entire policy for one keyword misses that distinction. Nonces, hashes and more specific directives can complicate the interpretation further.
The useful question is: what does this policy allow the browser to do?
MDN’s script-src reference explains the relevant behavior.
3. “CSP present” doesn’t mean scripts are restricted
This is a Content Security Policy:
Content-Security-Policy: frame-ancestors 'none'
It restricts embedding the page. By itself, it does not restrict script sources.
A scanner that only checks whether the header exists could award credit without examining which protections it provides.
The reverse also matters: a missing X-Frame-Options header doesn’t necessarily mean the page lacks framing protection. CSP’s frame-ancestors can provide it.
4. Report-only policies don’t enforce restrictions
These are different headers:
Content-Security-Policy
Content-Security-Policy-Report-Only
Report-only mode is useful for evaluating a policy before enforcing it. It does not block the actions that policy would prohibit.
Check the exact header name when a report seems inconsistent with browser behavior.
And test representative user flows before enforcing a policy. A configuration copied from another website can break your scripts, fonts or checkout.
5. The denominator can mislead you too
While reviewing a security-header study for my project, IsSiteSafe, I found problems in how the original results were summarized.
The saved data contained:
- 1,000 domain attempts.
- 648 recorded responses.
- 537 responses with both a successful 2xx status and an HTML content type.
Those are three different groups.
An error response is still a response. A failed fetch is an unknown outcome, not proof of missing headers. Even a successful HTML response might be a challenge page.
I corrected the report, removed unsupported conclusions and published all 1,000 saved outcomes with the limitations:
Security headers: a corrected 1,000-domain snapshot
What I’d compare before changing anything
- Final URL and HTTP status.
- Actual header values.
- Enforcement versus report-only.
- The scanner’s documented scoring rules.
- Browser behavior on representative pages.
A grade can help prioritize an investigation. It cannot establish that a website is free of phishing, malware or application vulnerabilities.
Have you had two scanners disagree about the same site? Which header or response difference explained it?
Top comments (0)