Live HTTP security header scanner with an A+ to F grade (HSTS, CSP, cookies, CORS, COOP/CORP, SRI) (MIT, free; pipx install) https://errant.solutions/tools/http-header-auditor.html
  • Python 87.8%
  • Shell 12.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Hermes Agent b06d4f5981
All checks were successful
tests / test (3.14) (push) Successful in 29s
tests / test (3.8) (push) Successful in 28s
ci: mutation-incremental.py shared-baseline version (copy of ctg-infrastructure 87ba7a5)
2026-10-21 20:25:58 -07:00
.forgejo/workflows mutation/chaos CI on every rw-man host again (owner decision 2026-10-18: colo backlog too big, GuardDuty suppression kept): EC2 guards are opt-in (refuse only with CI_COLO_ONLY=1, code kept), colo jobs move to the shared labels mutation-x86_64 / mutation-any (rw-man-01..05, 6g slots) 2026-10-18 13:45:35 -07:00
ci ci: mutation-incremental.py shared-baseline version (copy of ctg-infrastructure 87ba7a5) 2026-10-21 20:25:58 -07:00
tests Mutation testing: copy all of ci/ into mutmut's sandbox (also_copy of ci/check_claims.py failed: no mutants/ci dir), keep real sleeps in fixture-server threads (--timeout test), per-repo mutant parallelism (ci/mutation-children) 2026-10-16 23:56:24 -07:00
.gitignore nightly mutation: incremental (only functions changed since the last green run) except a weekly full run on the repo's own weekday; replace-queued concurrency; staggered cron 2026-10-18 12:22:50 -07:00
CHANGELOG.md Release 1.2.1: the test-depth fixes since 1.2.0 (see CHANGELOG) 2026-10-17 01:50:04 -07:00
claims.json Test depth: Observatory-table accuracy suite, nightly mutation + matrix + install channels, claims ledger; scoring/detection fixes it found 2026-10-16 21:51:42 -07:00
http_header_auditor.py Release 1.2.1: the test-depth fixes since 1.2.0 (see CHANGELOG) 2026-10-17 01:50:04 -07:00
LICENSE Add MIT LICENSE file 2026-10-16 14:07:53 -07:00
pyproject.toml Mutation testing: copy all of ci/ into mutmut's sandbox (also_copy of ci/check_claims.py failed: no mutants/ci dir), keep real sleeps in fixture-server threads (--timeout test), per-repo mutant parallelism (ci/mutation-children) 2026-10-16 23:56:24 -07:00
README.md Release 1.2.1: the test-depth fixes since 1.2.0 (see CHANGELOG) 2026-10-17 01:50:04 -07:00

http-header-auditor

A stdlib-only, zero-dependency live HTTP security header scanner. No API keys, no browser, no third-party services -- just Python 3's urllib.

What it checks

  • Strict-Transport-Security (HSTS): present? includeSubDomains? long enough max-age? Eligible for the browser preload list (1 year + includeSubDomains + preload)?
  • X-Content-Type-Options: nosniff present?
  • X-Frame-Options / CSP frame-ancestors: clickjacking protection? (only DENY/SAMEORIGIN count, browsers ignore other values; frame-ancestors * protects nothing)
  • Content-Security-Policy: present? overly permissive for scripts (unsafe-inline without a nonce/hash, unsafe-eval, wildcard default-src/script-src)? unsafe-inline for styles only is not a script hole and isn't penalised
  • Referrer-Policy: present, a value browsers understand, and not unsafe-url/no-referrer-when-downgrade?
  • Permissions-Policy: present?
  • Server / X-Powered-By: leaking stack/version info?
  • Set-Cookie: Secure / HttpOnly / SameSite on every cookie (each Set-Cookie header separately), SameSite=None without Secure, the __Host- (Secure, Path=/, no Domain) and __Secure- prefix rules, and session-looking cookies readable by scripts
  • CORS: the request carries a foreign Origin; an Access-Control-Allow-Origin that echoes it (FAIL with Allow-Credentials: true), or * (info)
  • Cross-Origin-Opener / Embedder / Resource-Policy (reported)
  • Subresource Integrity: cross-origin <script src> and stylesheets in the page need integrity= hashes (and must not load over plain HTTP)
  • Redirects: does plain http:// redirect to HTTPS, and does the first hop go to https:// on the same host (so the browser can pick up HSTS) before any cross-host redirect? (For an https:// URL on the default port the http:// twin is checked; if nothing listens on plain HTTP at all, that's reported as info.)

Every URL gets a score and grade (A+ to F) in the style of the Mozilla HTTP Observatory: start at 100, subtract penalties (no CSP -25, no HSTS -20, no clickjacking protection -20, no HTTP->HTTPS redirect -20, cookies without Secure -20, credentialed CORS for any origin -50, scripts over plain HTTP -50, ...), and add bonuses (preload-eligible HSTS, a CSP without unsafe-* (+5, +10 with default-src 'none'), frame-ancestors, strict Referrer-Policy, SameSite on every cookie, SRI everywhere) only once the score is 90 or more. Cookies count their worst single problem, as in the Observatory (a session cookie without HttpOnly -30, without Secure -40, or -10/-5 when HSTS already keeps cookies off plain HTTP). The numbers follow the Observatory's published table (tests/test_accuracy.py checks 18 header profiles against it) with two deliberate differences: preload eligibility earns the HSTS bonus (the tool can't see the browser preload list) and max-age=0 counts as no HSTS (-20, it switches HSTS off) rather than "under six months" (-10).

Install

pipx install git+https://git.canutethegreat.com/CanuteTheGreat/http-header-auditor.git        # latest release branch; puts `http-header-auditor` on PATH
pipx install git+https://git.canutethegreat.com/CanuteTheGreat/http-header-auditor.git@v1.2.1  # a pinned release
brew install canutethegreat/errant-tools/http-header-auditor   # after: brew tap canutethegreat/errant-tools https://git.canutethegreat.com/CanuteTheGreat/homebrew-errant-tools
scoop install errant-tools/http-header-auditor                 # after: scoop bucket add errant-tools https://git.canutethegreat.com/CanuteTheGreat/scoop-errant-tools

Or download the single file (stdlib-only Python 3.8+) and run it directly: curl -fsSLO https://errant.solutions/downloads/http_header_auditor.py && python3 http_header_auditor.py --help. Every release has a changelog, the wheel, the sdist and a SHA256SUMS file. Not on PyPI yet, so plain pipx install http-header-auditor doesn't work until it's published there.

Usage

http-header-auditor https://example.com [https://another.com ...]
http-header-auditor https://example.com --json
http-header-auditor https://example.com --fail-on weak        # exit 1 on WEAK too
http-header-auditor https://example.com --fail-on-grade B     # exit 1 if the grade is below B
http-header-auditor https://example.com --fail-on never       # report only

Exit codes (same convention as every Errant Solutions tool):

Code Meaning
0 no check at or above --fail-on (default missing: MISSING or FAIL) and the grade meets --fail-on-grade
1 otherwise
2 a target couldn't be fetched at all (connection or certificate error) and nothing else failed, or bad usage

--fail-on-missing-hsts still works (exit 1 on missing HSTS even with --fail-on never); --fail-on-any-missing is the same as the default. --timeout N sets the per-request timeout in seconds (default 10). Python 3.8+. 1.x users note: the tool used to exit 0 by default; it now fails on MISSING/FAIL checks like the other tools.

Why this exists

Part of the Errant Solutions toolbox: small, free, stdlib-only auditing tools built from spare capacity. Pay what you want if it's useful: see errant.solutions for the link.

License

MIT — see LICENSE. Do whatever you want with it.