Behind Cloudflare, the bot-detection script CF injects into every HTML
response carries a hash that rotates per request, so it can never be
allowlisted by hash. Reporters with CF in front had to relax their NPM
CSP to 'unsafe-inline' as a workaround.
Per Cloudflare's documented behaviour, when a nonce is present in the
page's script-src, CF clones it onto its injected <script>. The SPA CSP
now stamps a fresh per-request nonce via secrets.token_urlsafe(16),
keeping 'self' for our own scripts (index.html has had no inline scripts
since the SW registration moved to /sw-register.js in the original
#1460 PR), so no HTML body rewriting is needed.
Also folded in: /manifest.json, /sw.js and /sw-register.js now accept
HEAD as well as GET, so `curl -I` and uptime scanners stop returning
405 on those routes - a separate red herring during this issue's
debugging.
Tests: 3 new in test_security_headers.py - 'nonce-' token stamped into
SPA script-src while 'self' remains and 'unsafe-inline' does not; nonce
is fresh per request across 5 sequential calls; HEAD on the three PWA
routes never returns 405. 22/22 security-header tests green; backend
ruff clean.
* feat(auth): proxy OIDC provider icons server-side (#1333)
Strict img-src CSP blocked external OIDC icon hosts on the login page.
Loosening CSP was rejected via the MakerWorld precedent, so icons are
proxied: admin sets icon_url, backend fetches and caches the bytes in a
deferred BLOB column, the SPA renders from a same-origin
/api/v1/auth/oidc/providers/{id}/icon endpoint.
Bambuddy ships strict anti-clickjacking headers (X-Frame-Options:
SAMEORIGIN + CSP frame-ancestors 'none') by default. Internet-exposed
deployments need this; same-LAN HA Webpage-panel users do not, and
SAMEORIGIN is port-strict so HA on :8123 + Bambuddy on :8000 always
fails. azurusnova hit exactly that case.
Add TRUSTED_FRAME_ORIGINS env var (comma-separated scheme://host[:port]).
When set, drop X-Frame-Options entirely (modern browsers honor
frame-ancestors and the legacy ALLOW-FROM syntax is deprecated /
inconsistent across vendors) and emit "frame-ancestors 'self' <list>"
on every CSP-bearing route. Origin validation is strict: only http(s),
no paths, no query/fragment, no wildcards. Bad entries get a warning
and are dropped — startup never fails.
Default behaviour (no env var) is unchanged: X-Frame-Options:
SAMEORIGIN + frame-ancestors 'none', so existing Docker / bare-metal
deployments are not affected.