--- name: security-headers-and-tls description: Use when configuring or reviewing a web server/site's HTTP security headers and TLS/SSL setup. Baseline hardening to apply on any real site, not just when explicitly requested. --- # Security Headers and TLS ## HTTP security headers (baseline for any real site) ``` Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: geolocation=(), microphone=(), camera=() Content-Security-Policy: ``` - **HSTS** (`Strict-Transport-Security`): only send this once HTTPS is confirmed fully working site-wide (including all subdomains covered by `includeSubDomains`) — sending it prematurely can lock out HTTP access to a subdomain that isn't actually HTTPS-ready yet, and it's effectively irreversible for the `max-age` duration for anyone who already received it. - **X-Frame-Options** (or the modern equivalent, `frame-ancestors` in CSP): prevents clickjacking via iframe embedding. `SAMEORIGIN` unless the site genuinely needs to be embeddable elsewhere. - **Content-Security-Policy**: the highest-value header but also the easiest to get wrong and break the site with. Build it FROM the site's actual real asset sources (script/style/font/image origins actually in use), don't write a generic policy and hope — a too-strict CSP silently breaks functionality (a blocked script just doesn't run, often with no obvious visual error). Start in `Content-Security-Policy-Report-Only` mode if there's any uncertainty, confirm no violations in real browser testing, then switch to enforcing. - **CORS** (`Access-Control-Allow-Origin`): never `*` on any endpoint that reads authenticated/session state — an open CORS policy on an authenticated endpoint is a real, exploitable vulnerability, not a theoretical one. ## TLS/SSL configuration strength - **Protocol versions**: TLS 1.2 minimum, TLS 1.3 preferred/available. SSLv3, TLS 1.0, and TLS 1.1 should be disabled — they're broken or deprecated, not just "older." - **Cipher suites**: prefer AEAD ciphers (AES-GCM, ChaCha20-Poly1305). Disable known-weak ciphers (RC4, 3DES, export-grade ciphers, anything without forward secrecy). - **Certificate**: verify the chain is complete (intermediate certs included, not just the leaf), verify auto-renewal is actually working (a cert 5 days from expiry with no renewal cron running is a real incident waiting to happen, not a hypothetical), verify no mixed- content warnings (HTTP resources loaded on an HTTPS page). - **Test with a real tool**, don't eyeball a config file and assume it's correct: `openssl s_client -connect host:443 -tls1` (should fail if TLS 1.0 is properly disabled), or an external scanner (SSL Labs' `ssltest` API/site) for a full grade — config files that *look* right can still have an inherited default or a reverse-proxy layer re-enabling something the origin server itself disabled. ## Reverse-proxy-specific gotchas If the site sits behind a reverse proxy (nginx, a proxy manager, a CDN): security headers and TLS termination often happen at the PROXY layer, not the origin — verify headers on the actual public-facing response, not just what the origin server itself sends, since a proxy can add, strip, or override headers in either direction. Confirmed real pattern in this project: `X-Forwarded-Proto` set by the proxy and trusted by the origin's own HTTPS-detection logic — get this wrong in either direction (proxy not setting it, or origin not trusting it, or origin overwriting an already-correct value with its own guess) and you get either broken HTTPS detection or a spoofable header, depending on which mistake it is. ## Don't apply hardening blindly Test every header/TLS change against the real site afterward — a CSP that's too strict, an HSTS header sent too early, or a cipher suite change that drops support for a client that still needs to work are all real ways "hardening" can cause an outage. Security work still needs the same verification discipline as any other change.