# PiCloud production Caddyfile. # # Set PICLOUD_DOMAIN and PICLOUD_ADMIN_EMAIL in the environment Caddy is # started from (docker-compose.prod.yml passes them through). Caddy then # obtains and renews a Let's Encrypt cert automatically for that domain. # # Audit 2026-06-11 (H-A1/2/3/5, M07-06/07/08/09): defense-in-depth # headers. HSTS lands on every prod response. CSP, X-Frame-Options, # Permissions-Policy, and Cache-Control: no-store land on the dashboard # SPA and admin API. nosniff + Referrer-Policy land everywhere. User- # route responses (catch-all `handle`) deliberately get NO CSP — user # scripts own their own response headers by design. # `?` operator = "default if missing" so downstream responses (e.g. the # file-download endpoint with its own restrictive CSP) win. { email {$PICLOUD_ADMIN_EMAIL} } {$PICLOUD_DOMAIN} { encode zstd gzip # Baseline headers on every response. HSTS is unconditional in prod. header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" Referrer-Policy "no-referrer" } handle /healthz { reverse_proxy picloud:8080 } handle /version { reverse_proxy picloud:8080 } handle /api/v1/admin/* { header { ?Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; base-uri 'none'" ?X-Frame-Options "DENY" ?Cache-Control "no-store" ?Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()" } reverse_proxy picloud:8080 } handle /api/v1/execute/* { reverse_proxy picloud:8080 } handle /api/* { respond 404 { body "{\"error\":\"no such API version — see /version for supported routes\"}" close } } handle /admin/* { header { ?Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'" ?X-Frame-Options "DENY" ?Cache-Control "no-store" ?Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()" } reverse_proxy dashboard:80 } handle /admin { header { ?Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'" ?X-Frame-Options "DENY" } reverse_proxy dashboard:80 } handle { reverse_proxy picloud:8080 } log { output stdout format json } }