|
@@ -11,7 +11,7 @@
|
|
|
>
|
|
>
|
|
|
> Each finding is referenced as **F<N>** for later citation.
|
|
> Each finding is referenced as **F<N>** for later citation.
|
|
|
>
|
|
>
|
|
|
-> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (14 fixed, 13 open), 42 sev-1.
|
|
|
|
|
|
|
+> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (15 fixed, 12 open), 42 sev-1.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -837,6 +837,39 @@
|
|
|
exploitable without strict CSP. Migrating to nonces or hashed
|
|
exploitable without strict CSP. Migrating to nonces or hashed
|
|
|
scripts and packaging Alpine-with-CSP-build would close the gap.
|
|
scripts and packaging Alpine-with-CSP-build would close the gap.
|
|
|
- **Severity: 2**
|
|
- **Severity: 2**
|
|
|
|
|
+- **Status:** Fixed. `script-src` is now `'self' 'nonce-…'` only —
|
|
|
|
|
+ `'unsafe-inline'` and `'unsafe-eval'` are gone. Two changes close it:
|
|
|
|
|
+ 1. **Per-request CSP nonce.** `App\Http\CspMiddleware` mints a 16-byte
|
|
|
|
|
+ URL-safe nonce per request, exposes it on the request attribute and
|
|
|
|
|
+ as the Twig `csp_nonce` global, and stamps a fresh
|
|
|
|
|
+ `Content-Security-Policy: …; script-src 'self' 'nonce-…'; …` header
|
|
|
|
|
+ on the response. The static CSP block is removed from
|
|
|
|
|
+ `ui/docker/Caddyfile` because the nonce changes per response and
|
|
|
|
|
+ Caddy can't see that. The only inline `<script>` left in the
|
|
|
|
|
+ codebase — the FOUC dark-mode preloader in
|
|
|
|
|
+ `ui/resources/views/layout.twig` — carries `nonce="{{ csp_nonce }}"`.
|
|
|
|
|
+ 2. **Alpine.js CSP build.** Switched `ui/package.json` from `alpinejs`
|
|
|
|
|
+ to `@alpinejs/csp`, which never calls `Function()` and so does not
|
|
|
|
|
+ need `'unsafe-eval'`. The CSP build forbids inline expressions in
|
|
|
|
|
+ `x-data` / `x-on:` / `x-show` / `x-bind`, so every component lives
|
|
|
|
|
+ in `ui/resources/js/app.js` registered via `Alpine.data(name, …)`
|
|
|
|
|
+ (toggle, rowExpander, kindSwitcher, submitGuard, dangerousAction,
|
|
|
|
|
+ loginForm, decayPreview, policyPreview, policyScoreDistribution,
|
|
|
|
|
+ scoreOverTime, rawTokenCopy). Initial values are read from
|
|
|
|
|
+ `data-*` attributes on the root element, not interpolated into
|
|
|
|
|
+ attribute expressions. The audit-page datetime-local helper and
|
|
|
|
|
+ three previously per-page inline `<script>` blocks (categories,
|
|
|
|
|
+ ips/detail, policies) are inlined into `app.js`.
|
|
|
|
|
+ Regression tests:
|
|
|
|
|
+ - `ui/tests/Unit/Http/CspMiddlewareTest.php` covers nonce uniqueness,
|
|
|
|
|
+ URL-safe alphabet, the `script-src` + `frame-ancestors` shape, and
|
|
|
|
|
+ middleware integration.
|
|
|
|
|
+ - `ui/tests/Integration/App/CspHeaderTest.php` boots the full Slim
|
|
|
|
|
+ app and asserts: every response carries CSP, the layout
|
|
|
|
|
+ `<script nonce>` value matches the response header's
|
|
|
|
|
+ `'nonce-…'`, nonces rotate across requests, and no inline DOM
|
|
|
|
|
+ event handlers or `x-data="{…}"` object literals leak into the
|
|
|
|
|
+ rendered HTML.
|
|
|
|
|
|
|
|
### F25 — Trusted-proxy XFF rewrite + `private_ranges` may allow `/internal/*` bypass
|
|
### F25 — Trusted-proxy XFF rewrite + `private_ranges` may allow `/internal/*` bypass
|
|
|
- **Files:** `api/docker/Caddyfile:7-11, 50-62`,
|
|
- **Files:** `api/docker/Caddyfile:7-11, 50-62`,
|