SyntaxStudy
Sign Up
Web Security Security Header Auditing and Grading
Web Security Beginner 1 min read

Security Header Auditing and Grading

A well-secured web application should receive an A or A+ grade from security header scanners such as securityheaders.com and Mozilla Observatory (observatory.mozilla.org). These tools fetch your site's HTTP response headers and score them against a checklist: HSTS, X-Frame-Options or CSP `frame-ancestors`, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and Content-Security-Policy. They produce human-readable reports explaining each missing or misconfigured header. Integrating header auditing into CI/CD prevents regressions. Tools like `shcheck` (Python CLI), `observatory-cli`, or a custom cURL-based script can fail a pipeline if required headers are missing or set to insecure values. Some teams use `lighthouse-ci` with the security audits plugin to check headers as part of every deployment to staging. The order in which headers are set matters in some frameworks. Middleware applied later in the stack can overwrite headers set by earlier middleware. In Laravel, security header middleware should be registered as a global middleware with high priority so it runs on every response including error pages. Verify headers on 4xx and 5xx responses as well as 200 responses — attackers often probe error pages specifically because they sometimes bypass middleware.
Example
#!/usr/bin/env python3
# security_headers_check.py — CI/CD security header auditing script

import sys
import requests

REQUIRED_HEADERS = {
    'Strict-Transport-Security': lambda v: 'max-age=' in v and int(v.split('max-age=')[1].split(';')[0].strip()) >= 31536000,
    'X-Content-Type-Options':    lambda v: v.strip().lower() == 'nosniff',
    'X-Frame-Options':           lambda v: v.strip().upper() in ('DENY', 'SAMEORIGIN'),
    'Referrer-Policy':           lambda v: v.strip() in (
        'no-referrer', 'no-referrer-when-downgrade',
        'strict-origin', 'strict-origin-when-cross-origin',
    ),
    'Content-Security-Policy':   lambda v: "default-src" in v or "script-src" in v,
}

WARN_IF_PRESENT = ['X-Powered-By', 'Server']

def check(url: str) -> int:
    resp    = requests.get(url, timeout=10, allow_redirects=True)
    headers = {k.lower(): v for k, v in resp.headers.items()}
    errors  = 0

    for header, validator in REQUIRED_HEADERS.items():
        value = headers.get(header.lower())
        if value is None:
            print(f"[FAIL] Missing: {header}")
            errors += 1
        elif not validator(value):
            print(f"[FAIL] Weak:    {header}: {value}")
            errors += 1
        else:
            print(f"[PASS] OK:      {header}: {value[:60]}")

    for header in WARN_IF_PRESENT:
        if header.lower() in headers:
            print(f"[WARN] Leaks info: {header}: {headers[header.lower()]}")

    return errors

if __name__ == '__main__':
    url    = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
    errors = check(url)
    print(f"\n{'PASS' if errors == 0 else 'FAIL'}: {errors} issue(s) found")
    sys.exit(1 if errors else 0)