From de7df3f390d88d4d7e2e9f04d783fae87f95d01b Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Mon, 27 Apr 2026 15:48:22 +0200 Subject: [PATCH] v1.7.13: switch Alpine.js to CSP build, remove unsafe-eval from CSP --- RELEASE_NOTES_v1.7.13.md | 34 ++++++++++++++++++++++++++++++++++ VERSION | 2 +- backend/frontend/index.html | 10 +++++++++- backend/main.py | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 RELEASE_NOTES_v1.7.13.md diff --git a/RELEASE_NOTES_v1.7.13.md b/RELEASE_NOTES_v1.7.13.md new file mode 100644 index 0000000..72a5e72 --- /dev/null +++ b/RELEASE_NOTES_v1.7.13.md @@ -0,0 +1,34 @@ +# AOC v1.7.13 Release Notes + +**Release Date:** 2026-04-27 + +## Security Hardening: Alpine.js CSP Build + +This release removes `unsafe-eval` from the Content-Security-Policy by switching the frontend to Alpine.js's CSP-compatible build. + +### Changes + +- **Frontend:** Switched from `alpinejs@3.x.x/dist/cdn.min.js` to `alpinejs@3.x.x/dist/csp.min.js` +- **Frontend:** Added explicit `Alpine.start()` call on `DOMContentLoaded` (required by CSP build) +- **Backend CSP:** Removed `'unsafe-eval'` from `script-src` directive + +### Why this matters + +The previous v1.7.11–1.7.12 releases included `'unsafe-eval'` in the CSP because the standard Alpine.js CDN build uses `new Function()` internally for reactive expression evaluation. The CSP build eliminates this requirement, further hardening the application against XSS and injection attacks. + +### Compatibility + +All existing Alpine.js directives (`x-data`, `x-init`, `x-show`, `x-text`, `x-for`, `x-if`, `x-model`, event handlers) continue to work unchanged. The CSP build uses a safe expression evaluator that produces identical behavior without `eval`/`new Function`. + +## Files Changed + +| File | Change | +|------|--------| +| `backend/frontend/index.html` | Alpine.js src → `csp.min.js`; added `Alpine.start()` | +| `backend/main.py` | Removed `'unsafe-eval'` from `script-src` CSP | +| `VERSION` | Bumped to 1.7.13 | + +## Test Results + +- **80/80 pytest tests passing** +- Ruff lint/format clean diff --git a/VERSION b/VERSION index e6a68e9..36c5cb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.12 +1.7.13 diff --git a/backend/frontend/index.html b/backend/frontend/index.html index d49c4bc..42feb48 100644 --- a/backend/frontend/index.html +++ b/backend/frontend/index.html @@ -5,7 +5,7 @@ Admin Operations Center - + @@ -1274,5 +1274,13 @@ }; } + diff --git a/backend/main.py b/backend/main.py index 36a1b53..c29a906 100644 --- a/backend/main.py +++ b/backend/main.py @@ -109,7 +109,7 @@ async def security_headers_middleware(request: Request, call_next): if request.url.path.startswith("/api/") or request.url.path in ("/", "/index.html"): response.headers["Content-Security-Policy"] = ( "default-src 'self'; " - "script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.jsdelivr.net alcdn.msauth.net; " + "script-src 'self' 'unsafe-inline' cdn.jsdelivr.net alcdn.msauth.net; " "style-src 'self' 'unsafe-inline'; " "connect-src 'self' https://login.microsoftonline.com; " "frame-src 'self' https://login.microsoftonline.com; "