All notable changes to headersvalidator are documented in this file.
Format follows Keep a Changelog. Version numbers follow Semantic Versioning.
0.2.3 — 2026-07-08
reporter.py: results table now usesexpand=Trueandratio=1on the "Reason / Value" column so the table always fills the terminal width and never clips content when the terminal is narrower than the former fixed minimum width.reporter.py:save_report()now re-renders the report on a fresh 220-column console (with output redirected toStringIO) instead of saving the terminal-width-constrained recording. This prevents exported.txt,.svg, and.htmlfiles from being truncated by the terminal width, and stops the report from being printed a second time to the terminal when saving a file.cli.py: updated thesave_report()call site to passreport, matching the signature change above.--outputwas raising aTypeErroron every invocation until this fix.
- Reverted the
ℹ/⚠→ ASCIIi/!status-symbol substitution (previously logged here as a fix for SVG/text column misalignment). Root-caused: the misalignment is not caused by these specific glyphs — it reproduces on plain box-drawing border characters too, in any SVG viewer that doesn't implementtextLength/lengthAdjustscaling (many OS image previews and lightweight SVG rasterizers ignore it entirely and use natural glyph advance). Avoiding individual glyphs cannot fix a viewer capability gap, so the original Unicode symbols are restored.
0.2.2 — 2026-06-30
reporter.py: removed IANA and Source columns from the terminal results table; the Reason / Value column now has more display width.
0.2.1 — 2026-06-24
checker.py:check_headerdocstring now documents bothNonereturn cases — unregistered header name, and deprecated/obsoleted header absent from the response.
tests/test_checker.py:TestCheckAll.test_deprecated_header_present_included_in_results— verifies that a deprecated header which IS present in the response appears incheck_all()results (previously only tested viacheck_header).
0.2.0 — 2026-06-24
checker.py: deprecated and obsoleted headers (Expect-CT,Feature-Policy) are now omitted entirely from results when absent. The only actionable state for these headers is when they are present (flaggedDEPRECATED). Showing them absent added noise with no actionable value.checker.py:_check_onereturn type is nowHeaderResult | None;check_allandcheck_headerhandleNonegracefully.
- v0.1.8/v0.1.9 intermediate message
"Header is absent (deprecated — do not add it)."— superseded by the omit-entirely approach.
0.1.9 — 2026-06-24
checker.py: absent-optional branch now also matchesiana_status="obsoleted"(coversExpect-CT); v0.1.8 only matched"deprecated"soExpect-CTstill showed "Consider adding it." instead of "do not add it."
0.1.8 — 2026-06-24
checker.py: absent deprecated headers (Expect-CT,Feature-Policy) now show"Header is absent (deprecated — do not add it)."instead of the misleading"Header is absent (optional). Consider adding it.".
0.1.7 — 2026-06-24
README.md: corrected stale HSTS max-age threshold (1 year → 2 years) in the feature table to match_hsts_checkbehaviour introduced in v0.1.6.README.md: addedpaymentto the Permissions-Policy sensitive-features list; corrected the required-flag column from—to✔.README.md: corrected CRITICAL penalty from 10 pts to 15 pts in the grading table.README.md: corrected grade thresholds to match_GRADE_THRESHOLDS(1–5 → A,6–20 → B,21–40 → C,41–60 → D,> 60 → F).README.md: added missing Feature-Policy row to the header table.
0.1.6 — 2026-06-24
constants._feature_policy_check: new check function that flagsFeature-PolicyasDEPRECATED— the header is the deprecated predecessor ofPermissions-Policyand should be removed.HEADER_RULES: 15th entry forFeature-Policy(required=False,iana_status="deprecated").tests/test_checker.py: 9 new tests covering CSP wildcard/data-URI/object-src/base-uri branches, Permissions-Policy value-check branches, and Feature-Policy deprecated/absent cases.
constants._hsts_check: HSTS preload list requiresmax-age ≥ 63 072 000(2 years); previous threshold was 31 536 000 (1 year).constants._csp_check:unsafe-inlineandunsafe-evalnow produceFAIL(notWARN); wildcard anddata:sources inscript/default-srcalso produceFAIL; missingobject-srcandbase-uriproduceWARN;default-src 'none'exempts theobject-srcwarning.constants._cache_control_check: directive parsing is now token-based (split on,, extract name before=), eliminating false positives on directive names that contain substrings ofpublic.constants._permissions_check:paymentadded to sensitive feature list; check now verifies the feature value equals()(not just that the feature name appears), preventing bypasses viageolocation=(self).constants._server_check: giveaways regex expanded with additional server/framework names (jetty, tomcat, kestrel, node.js, express, flask, django, fastapi, ruby, passenger, bun, deno, haproxy, kong, traefik, varnish, envoy).verdict._PENALTY: CRITICAL penalty raised from 10 to 15 points.verdict._GRADE_THRESHOLDS: updated to[(0,"A+"), (5,"A"), (20,"B"), (40,"C"), (60,"D")]; 1 missing HSTS (15 pts) now yields B, not A.
0.1.5 — 2026-06-24
verdict.VerdictSeverity: removed deadLOWenum value — no code path inextract_verdict_actions()ever produced a LOW action, making the entry unreachable dead code that also caused a latentKeyErrorinreporter.py.constants._permissions_check: replaced fragile bare substring match with token-based feature-name parsing (part.split("=")[0]), preventing false negatives when feature names overlap as substrings.constants._referrer_check: removed dead"no-referrer-when-downgrade"entry from thesafeset — it was caught by the explicitWARNbranch above and never reached the set.
models.py,http_utils.py,checker.py: migrated allOptional[X]type annotations toX | None(PEP 604) per platform convention; removedfrom typing import Optionalfrom each file.CLAUDE.md: corrected stale coverage target note (≥ 80%→100%) to match--cov-fail-under=100enforcement added in this batch.
- Test for
assess(..., progress_cb=...)covering the previously uncovered branch atassessor.py:69; 100% test coverage restored. pyproject.toml:--cov-fail-under=100added to pytestaddoptsto machine-enforce the coverage target on every run.
0.1.4 — 2026-05-15
pytest-mock>=3.12added to dev extras.
assessor.assess(): parameters afterurlare now keyword-only (enforced by*,), preventing accidental positional mis-binding by callers.cli:--versionshort flag changed from-vto-V(uppercase), consistent with all other platform modules.reporter.save_report(): now raisesValueErrorfor unsupported file extensions instead of silently falling back to plain text, aligning behaviour with the existing docstring.verdict:VerdictSeveritygains aLOWmember (1 pt penalty) betweenMEDIUMandINFO, completing the five-tier severity vocabulary shared withmailvalidator.
0.1.3 — 2026-05-15
assessor.assess()accepts an optionalprogress_cb: Callable[[str], None]parameter, consistent with the platform-wide public API convention.reportermodule now exposes a publicconsolealias (Console(record=True)) and asave_report(path)function; the CLI imports these rather than duplicating the export logic internally.
- CLI migrated to use
reporter.consoleandreporter.save_report()— the private_save_report()helper incli.pyhas been removed.
0.1.2 — 2026-04-08
- Report header now shows the final redirect destination URL when a redirect was followed during the header fetch.
0.1.1 — 2026-04-08
- HTTP redirect followed correctly when the
https://port is closed and the server redirects tohttp://.
0.1.0 — 2026-04-08
- Initial release of headersvalidator.
- HTTP security-header validation against RFC 9110/9111, OWASP, and the IANA header registry.
- Scoring model:
PASS/WARN/FAIL/DEPRECATED/INFOstatus per header; letter-grade verdict (A+…F) derived from weighted penalty points. - Security Verdict panel with actionable recommendations surfaced in the terminal report.
- CLI:
headersvalidator check <url-or-domain>with--json,--output,--strictflags. headersvalidator info rules— lists all evaluated header rules.- Report export to
.txt,.svg,.html. - HEAD with GET fallback (on HTTP 405) for header fetching.