c1db5cec86
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.
2.8 KiB
2.8 KiB
Security posture
This site is meant to demonstrate the discipline it advertises. Security is treated as acceptance criteria, not polish.
Attack surface
- Static output — no server runtime, no database, no user input, no forms.
- Served by
nginx-unprivileged(uid 101, read-only root filesystem, all Linux capabilities dropped, no service-account token) on Kubernetes. - Exposed outbound-only via a Cloudflare Tunnel — no open inbound ports, a single public hostname, no catch-all.
Headers
Split by where they belong:
Origin (nginx, ships in the image — nginx/security-headers.conf)
| Header | Value |
|---|---|
| Content-Security-Policy | default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests |
| X-Content-Type-Options | nosniff |
| X-Frame-Options | DENY |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera/mic/geo/payment/usb… all denied |
| Cross-Origin-Opener-Policy | same-origin |
| Cross-Origin-Resource-Policy | same-origin |
Edge (Cloudflare dashboard) — HSTS, HTTP→HTTPS redirect, SSL Full, Bot Fight,
rate-limiting. HSTS ships without includeSubDomains/preload initially because
*.bztmon.com resolves to the WAN and the subdomain form would brick non-public hosts.
CSP notes
script-src 'self'with zero inline scripts. All JS (pre-paint theme, toggle, scroll reveal) lives in one external/site.js. Nounsafe-inline, nounsafe-eval, no hashes to maintain.style-srcallows'unsafe-inline'— the one conscious exception. Shiki's dual-theme syntax highlighting emits per-token CSS custom-properties as inlinestyleattributes; hashing them is impractical (they vary per page). The security-critical directive (script-src) stays strict. Everything else is self-hosted: fonts are system stacks (zero font requests), no third-party scripts, so SRI is moot.- CSP is emitted as a real HTTP header (not
<meta>) soframe-ancestorsis honoured and header scanners can see it.
Build-time gate
npm run scan (scripts/check-build.sh) fails the build on:
- secret-like strings in
dist/ - any inline
<script>(would break the strict CSP) - third-party
<script>/<link>origins
Runs in CI before the image is built.
Targets (verified post-deploy)
- securityheaders.com → A+
- Mozilla Observatory → A/A+ (small deduction expected for
style-src 'unsafe-inline') - Lighthouse ≥ 95 across Performance / Accessibility / Best Practices / SEO
- CSP: zero console violations in a real browser (incl. code blocks + diagrams)
Reporting
Found something? Email the address on the site.