"""Test that security headers are present on all HTTPS sites.""" import glob import os import re import urllib.request import ssl import pytest CONF_DIR = os.path.join(os.path.dirname(__file__), "..", "docker", "nginx", "conf.d") def discover_sites(): """Build HTTPS URLs from nginx conf files that include security-headers.inc.""" sites = [] for conf in sorted(glob.glob(os.path.join(CONF_DIR, "*.conf"))): text = open(conf).read() if "security-headers.inc" not in text: continue # Use the first server_name in the block that includes the headers for block in re.split(r"(?=server\s*\{)", text): if "security-headers.inc" in block: m = re.search(r"server_name\s+([^;]+);", block) if m: sites.append(f"https://{m.group(1).split()[0]}") break return sites SITES = discover_sites() EXPECTED_HEADERS = { "Strict-Transport-Security": "max-age=63072000; preload", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "Referrer-Policy": "strict-origin-when-cross-origin", "Permissions-Policy": "geolocation=(), microphone=(), camera=()", } def get_headers(url): ctx = ssl.create_default_context() req = urllib.request.Request(url, method="HEAD") try: resp = urllib.request.urlopen(req, context=ctx, timeout=10) return dict(resp.headers) except urllib.error.HTTPError as e: return dict(e.headers) @pytest.mark.parametrize("url", SITES, ids=lambda u: u.split("//")[1]) def test_security_headers(url): headers = get_headers(url) missing = [] for name, expected in EXPECTED_HEADERS.items(): actual = headers.get(name) if actual != expected: missing.append(f"{name}: expected '{expected}', got '{actual}'") assert not missing, "\n".join(missing)