Files
plm-lottery/Caddyfile
davideandClaude Sonnet 5 d60da11603 Add baseline HTTP security headers in Caddy (B-43)
Caddy adds none of these on its own. Add HSTS, X-Content-Type-Options,
X-Frame-Options, Referrer-Policy and a CSP scoped to default-src
'self' plus the one external asset (Google Fonts). script-src/style-src
need 'unsafe-inline' because both SPAs rely on inline onclick handlers
and style="" attributes throughout — removing those is a separate,
larger refactor.

Validated with `caddy validate` and a live container curl check.
Adds a static regression test asserting the header directives stay
present in the Caddyfile, since nothing else in the Python suite
exercises it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 15:44:47 +02:00

34 lines
1.7 KiB
Caddyfile

# SITE_ADDRESS is the domain to serve (e.g. lottery.example.com) — Caddy
# automatically requests a Let's Encrypt certificate for it.
#
# Left at the default "localhost" (dev mode, no domain), Caddy detects it's
# not a public hostname and issues a locally-trusted self-signed certificate
# instead, via its internal CA. Browsers will still warn on first visit
# unless that CA is explicitly trusted — expected for local/dev use.
{$SITE_ADDRESS:localhost} {
# gzip buffers output, which would delay delivery on the SSE stream
# (/rounds/stream, app/api/routes/rounds.py) — it needs each event flushed
# to the client immediately, not batched. Everything else still compresses.
@not_sse {
not path /rounds/stream
}
encode @not_sse gzip
# B-43: Caddy adds none of these on its own. The JWT lives in
# localStorage, so any XSS exfiltrates it — CSP is the main mitigation.
# script-src/style-src need 'unsafe-inline' because both SPAs
# (app/static/index.html, admin.html) use inline onclick handlers and
# style="" attributes throughout; removing those is a separate,
# larger refactor, not a header change. fonts.googleapis.com/gstatic.com
# are the one external asset (the Google Fonts @import in style.css/admin.css).
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'"
}
reverse_proxy app:8123
}