M4: security pass — strict CSP, header split, build-time scan

All JS moved to external /site.js → script-src 'self' with no inline JS,
hashes or eval. Full header set via nginx (CSP, nosniff, frame-deny,
referrer, permissions, COOP/CORP); HSTS stays at the CF edge. Shared
headers include avoids the location add_header reset footgun. Build-time
secret/inline-script/third-party scan gate. SECURITY.md documents posture.
This commit is contained in:
2026-06-17 17:12:57 +10:00
parent cb76a87c36
commit c1db5cec86
9 changed files with 210 additions and 64 deletions
+12 -12
View File
@@ -11,28 +11,28 @@ server {
# Don't leak the nginx version.
server_tokens off;
# ---- Security headers (origin) -------------------------------------------
# These travel with the artifact. HSTS + HTTPS redirect are set at the
# Cloudflare edge (the tunnel terminates TLS), so they are NOT set here.
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
# ---- Content-Security-Policy ---------------------------------------------
# FILLED IN M4: Astro computes the inline script/style hashes at build; the
# final policy is emitted here as a header (a <meta> CSP can't set
# frame-ancestors). Until then, a conservative baseline:
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
# Security headers (CSP, nosniff, frame, etc.) — applied site-wide.
# Re-included in each location below that sets its own add_header, because
# a location-level add_header drops all inherited ones.
include /etc/nginx/security-headers.conf;
# ---- Caching -------------------------------------------------------------
# Astro emits content-hashed assets under /_astro/ — cache them hard.
location /_astro/ {
include /etc/nginx/security-headers.conf;
expires 1y;
add_header Cache-Control "public, immutable" always;
}
# Non-fingerprinted top-level script — revalidate so updates propagate.
location = /site.js {
include /etc/nginx/security-headers.conf;
add_header Cache-Control "no-cache" always;
}
# HTML is revalidated so deploys show up immediately.
location ~* \.html$ {
include /etc/nginx/security-headers.conf;
add_header Cache-Control "no-cache" always;
}