# FrankenPHP Caddyfile for the ui container.
# Serves Slim from public/ on :8080.
{
frankenphp
order php_server before file_server
auto_https off
admin off
servers {
trusted_proxies static private_ranges
}
}
:8080 {
root * /app/public
encode zstd gzip
# ── Security headers (M14) ──────────────────────────────────────────
# CSP is set per-response by `App\Http\CspMiddleware` so the
# `script-src 'nonce-…'` value can change per request, dropping
# `'unsafe-inline'` / `'unsafe-eval'` (SEC_REVIEW F24).
header {
-Server
-X-Powered-By
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
# SEC_REVIEW F61: extended deny-list for browser features
# the admin UI never uses. The narrow `geolocation=(),
# microphone=(), camera=()` was the M14 starter; this is the
# full hardening list. `clipboard-read` is blocked but
# `clipboard-write` is left at its same-origin default so
# the "copy raw token" button (rawTokenCopy Alpine
# component) still works on the Tokens page.
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), serial=(), speaker-selection=(), usb=(), web-share=(), xr-spatial-tracking=()"
# SEC_REVIEW F59: modern cross-origin isolation headers.
# - COOP `same-origin` isolates the browsing context from any
# popups it opens; a `window.opener.location = …` from a
# newly-spawned cross-origin tab can no longer reach back.
# - CORP `same-origin` tells the browser this resource may
# only be loaded by same-origin documents (defeats sub-
# resource leaks via cross-origin
/