How to Audit Security Headers with Python: Grade Any Website A+ to F
Why Security Headers Matter (and Why Most Sites Fail)
HTTP security headers are directives that your web server sends to the browser, instructing it how to behave when handling your site's content. They are a first line of defense against entire classes of attacks: cross-site scripting, clickjacking, MIME sniffing, protocol downgrade attacks, and unauthorized feature access.
The six headers that matter most are:
- Strict-Transport-Security (HSTS) — forces HTTPS for all future requests, preventing SSL stripping attacks
- Content-Security-Policy (CSP) — controls which resources the browser is allowed to load, blocking XSS payloads
- X-Frame-Options — prevents your site from being embedded in iframes, stopping clickjacking
- X-Content-Type-Options — blocks MIME-type sniffing, forcing the browser to respect the declared content type
- Referrer-Policy — controls how much referrer information is sent with outgoing requests
- Permissions-Policy — restricts which browser features (camera, microphone, geolocation) your site can use
Despite being straightforward to configure, the majority of websites fail basic security header checks. A 2025 analysis of the Alexa top 1 million sites found that fewer than 15% had a Content-Security-Policy header at all, and fewer than 5% had one strict enough to meaningfully prevent XSS. HSTS adoption is higher but still sits below 30% for most verticals outside banking and fintech.
For DevOps teams, security engineers, and compliance officers, the problem is not just setting these headers once. It is monitoring them continuously across every domain you manage, every third-party vendor you onboard, and every subdomain that spins up. Manual checks do not scale. You need automation.
The Manual Way: Checking Headers with Python + Requests
The simplest approach is to send an HTTP request and inspect the response headers. Python's requests library makes this a few lines of code:
import requests
url = "https://stripe.com"
resp = requests.get(url, timeout=10)
headers_to_check = [
"Strict-Transport-Security",
"Content-Security-Policy",
"X-Frame-Options",
"X-Content-Type-Options",
"Referrer-Policy",
"Permissions-Policy",
"X-XSS-Protection",
"Cross-Origin-Opener-Policy",
]
print(f"Security headers for {url}\n")
for header in headers_to_check:
value = resp.headers.get(header, "MISSING")
status = "PASS" if value != "MISSING" else "FAIL"
print(f" [{status}] {header}: {value[:80]}")
This gets you a quick spot-check. But the moment you need to go beyond presence/absence, the complexity explodes:
- HSTS validation — is the
max-agelong enough? Does it includeincludeSubDomains? Is the site on the HSTS preload list? - CSP analysis — does the policy use
unsafe-inlineorunsafe-eval? Are there wildcard sources? Isdefault-srcset? - Scoring — how do you weight a missing Permissions-Policy against a weak CSP? What constitutes a passing grade?
- Edge cases — redirects that drop headers, CDN layers that strip them, multiple CSP headers that need merging
Building a reliable scoring engine on top of raw header parsing is hundreds of lines of code. And you have to maintain it as browser behavior and best practices evolve. This works for one site. But what about 100?
The API Way: One Call, Full Security Report
The DetectZeStack /security endpoint does the parsing, validation, and grading for you. One HTTP request returns a structured report with a letter grade, a numeric score, and detailed per-header test results.
Here is what a raw curl request looks like:
curl -s "https://detectzestack.p.rapidapi.com/security?url=stripe.com" \
-H "x-rapidapi-key: YOUR_API_KEY" \
-H "x-rapidapi-host: detectzestack.p.rapidapi.com" | python3 -m json.tool
And here is the response you get back:
{
"url": "https://stripe.com",
"domain": "stripe.com",
"grade": "A",
"score": 100,
"max_score": 130,
"scan_time_ms": 342,
"cached": false,
"tests": {
"strict-transport-security": {
"pass": true,
"score_modifier": 20,
"result": "HSTS enabled with sufficient max-age",
"value": "max-age=63072000; includeSubDomains; preload",
"info": "max-age is 63072000 seconds (730 days)"
},
"content-security-policy": {
"pass": true,
"score_modifier": 20,
"result": "CSP header present",
"value": "default-src 'none'; script-src ...",
"info": "Policy defines default-src directive"
},
"x-frame-options": {
"pass": true,
"score_modifier": 15,
"result": "X-Frame-Options set",
"value": "SAMEORIGIN",
"info": "Clickjacking protection enabled"
},
"x-content-type-options": {
"pass": true,
"score_modifier": 15,
"result": "nosniff enabled",
"value": "nosniff",
"info": "MIME-type sniffing blocked"
},
"referrer-policy": {
"pass": true,
"score_modifier": 10,
"result": "Referrer-Policy set",
"value": "strict-origin-when-cross-origin",
"info": "Referrer leakage controlled"
},
"permissions-policy": {
"pass": true,
"score_modifier": 10,
"result": "Permissions-Policy set",
"value": "camera=(), microphone=(), geolocation=()",
"info": "Browser feature access restricted"
},
"x-xss-protection": {
"pass": true,
"score_modifier": 10,
"result": "X-XSS-Protection enabled",
"value": "1; mode=block",
"info": "Legacy XSS filter enabled"
},
"cross-origin-opener-policy": {
"pass": true,
"score_modifier": 10,
"result": "COOP header set",
"value": "same-origin",
"info": "Cross-origin window isolation enabled"
}
}
}
The grade scale works as follows: A+ requires a score of 110 or higher, A is 100-109, B is 80-99, C is 60-79, D is 40-59, and F is anything below 40. The maximum possible score is 130.
Here is how you call the same endpoint from Python:
import requests
url = "https://detectzestack.p.rapidapi.com/security"
params = {"url": "stripe.com"}
headers = {
"x-rapidapi-key": "YOUR_API_KEY",
"x-rapidapi-host": "detectzestack.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
print(f"Domain: {data['domain']}")
print(f"Grade: {data['grade']} ({data['score']}/{data['max_score']})")
print(f"Scan: {data['scan_time_ms']}ms\n")
for header, result in data["tests"].items():
status = "PASS" if result["pass"] else "FAIL"
print(f" [{status}] {header}")
print(f" {result['result']}")
print(f" {result['info']}\n")
Free tier includes 100 requests/month. Get your API key on RapidAPI — no credit card required.
Scanning Multiple Sites in Bulk
The real power of an API-based approach shows when you need to audit dozens or hundreds of domains. Here is a complete Python script that reads domains from a list, calls the /security endpoint for each, and outputs a graded report:
import requests
import time
API_URL = "https://detectzestack.p.rapidapi.com/security"
HEADERS = {
"x-rapidapi-key": "YOUR_API_KEY",
"x-rapidapi-host": "detectzestack.p.rapidapi.com"
}
domains = [
"stripe.com",
"github.com",
"shopify.com",
"wordpress.org",
"example.com",
]
print(f"{'Domain':<25} {'Grade':<6} {'Score':<8} {'Missing Headers'}")
print("-" * 75)
for domain in domains:
resp = requests.get(API_URL, headers=HEADERS, params={"url": domain})
data = resp.json()
missing = [
name for name, test in data["tests"].items()
if not test["pass"]
]
missing_str = ", ".join(missing) if missing else "none"
print(f"{data['domain']:<25} {data['grade']:<6} "
f"{data['score']}/{data['max_score']:<5} {missing_str}")
time.sleep(0.5) # be respectful of rate limits
Sample output:
Domain Grade Score Missing Headers
---------------------------------------------------------------------------
stripe.com A 100/130 none
github.com A 105/130 none
shopify.com B 85/130 permissions-policy, cross-origin-opener-policy
wordpress.org C 65/130 content-security-policy, permissions-policy, cross-origin-opener-policy
example.com F 20/130 strict-transport-security, content-security-policy, x-frame-options, permissions-policy, cross-origin-opener-policy
You can extend this script to write results to a CSV, send Slack alerts for any domain scoring below a threshold, or integrate it into a CI/CD pipeline that blocks deploys when headers regress.
Try the live demo at detectzestack.com/security-headers — scan any domain, no API key needed.
Understanding the Results: What Each Header Test Means
The API tests eight security headers. Here is what each one does, how the API determines a pass, and what you risk if it is missing:
| Header | Purpose | Pass Criteria | Risk if Missing |
|---|---|---|---|
| Strict-Transport-Security | Enforces HTTPS for all requests | Present with max-age ≥ 1 year |
SSL stripping, man-in-the-middle |
| Content-Security-Policy | Controls allowed resource origins | Header present with a defined policy | XSS, data injection, code execution |
| X-Frame-Options | Prevents iframe embedding | Set to DENY or SAMEORIGIN | Clickjacking attacks |
| X-Content-Type-Options | Blocks MIME-type sniffing | Set to nosniff |
MIME confusion, drive-by downloads |
| Referrer-Policy | Controls referrer information leakage | Header present with a valid policy | URL/token leakage to third parties |
| Permissions-Policy | Restricts browser feature access | Header present | Unauthorized camera/mic/geo access |
| X-XSS-Protection | Legacy browser XSS filter | Set to 1; mode=block |
XSS in older browsers |
| Cross-Origin-Opener-Policy | Isolates browsing context from cross-origin windows | Header present | Cross-origin data leaks via window references |
Each test contributes a score_modifier to the total. HSTS and CSP are weighted highest at 20 points each because they address the most critical attack vectors. The remaining headers contribute 10-15 points each. For a deeper dive into each header's security implications, see the OWASP Secure Headers Project.
Real-World Use Cases
Automated security header scanning fits into several workflows beyond one-off audits:
- PCI-DSS compliance — PCI DSS 4.0 Requirement 6.4.3 explicitly requires Content-Security-Policy headers for payment pages. Automated scans give you audit-ready evidence that your headers are in place and correctly configured.
- Vendor and third-party assessments — before onboarding a SaaS vendor or integrating a third-party widget, scan their domain. A site scoring D or F on security headers is a red flag worth raising in your vendor risk assessment.
- Pre-launch security checklists — add a security header scan to your deployment pipeline. If the grade drops below your threshold (say, B), fail the deploy. This catches header regressions caused by server config changes or CDN misconfigurations.
- Competitive security benchmarking — scan your competitors alongside your own domains. If you score A and they score C, that is a trust differentiator you can put on your security page.
Getting Started
The DetectZeStack security headers API is available on RapidAPI with a free tier of 100 requests per month. No credit card is required to get started.
- Get your free API key on RapidAPI
- Call
GET /security?url=yourdomain.comwith your key - Get a grade, score, and per-header breakdown in the response
You can also scan any domain interactively on the Security Headers landing page — no API key needed, no signup required.
If you are building a broader security audit workflow, the DetectZeStack API also offers technology detection and CPE-based vulnerability matching, so you can combine header grades with dependency risk in a single pipeline.
For quick one-off checks while browsing, the DetectZeStack Chrome extension shows technology and security data for any page you visit.
Related Reading
- Security Audit Website Dependencies with CPE — Identify vulnerable libraries and frameworks with CPE-based scanning
- Detect Any Website Tech Stack with a Single API Call — Full technology detection across 7,200+ technologies
- Website Technology Detection Python Tutorial — Step-by-step guide to tech stack detection with Python
Try DetectZeStack Free
100 requests per month, no credit card required. Grade security headers on any website from A+ to F with a single API call.
Get Your Free API Key