diff --git a/CHANGELOG.md b/CHANGELOG.md index 4566e07b7..b9b3caa04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,23 @@ All notable changes to this project are documented in this file. The format is b ## [Unreleased] ### Changed +- **Breaking (production CSP, #785):** `script-src`/`script-src-elem` are now + `'self' 'nonce-' 'strict-dynamic'` (plus `'wasm-unsafe-eval'` + for WebAssembly) instead of `'unsafe-inline' 'unsafe-eval'`. The host and + scaffolded templates, the Inertia entry tags and SPA-mount shells carry the + nonce. Upgrade: custom templates must put + `nonce="{{ request.state.csp_nonce }}"` on every executable `' + '' + '' + '' +) + + +def _scope(nonce: str | None) -> dict: + state = {"csp_nonce": nonce} if nonce else {} + return {"type": "http", "method": "GET", "path": "/", "headers": [], "state": state} + + +async def test_index_scripts_carry_the_request_nonce(tmp_path) -> None: + (tmp_path / "index.html").write_text(_SHELL, encoding="utf-8") + spa = SpaStaticFiles(directory=tmp_path, index="index.html") + + response = await spa.get_response("", _scope("abc123")) + + body = response.body.decode() + assert '' + f'' ) if inertia.is_ssr: if inertia.ssr_head is None: @@ -56,6 +64,7 @@ def _render_inertia_head(self, context: Context) -> Markup: def _render_inertia_body(self, context: Context) -> Markup: inertia: InertiaContext = context["inertia"] + nonce_attr = _nonce_attr(context) fragments: list[str] = [] if inertia.is_ssr: if inertia.ssr_body is None: @@ -69,5 +78,5 @@ def _render_inertia_body(self, context: Context) -> Markup: f'' ) fragments.append('
') - fragments.append(f'') + fragments.append(f'') return Markup("\n".join(fragments)) diff --git a/host/templates/index.html b/host/templates/index.html index 5fb91771a..83fc682d0 100644 --- a/host/templates/index.html +++ b/host/templates/index.html @@ -13,11 +13,9 @@ React mounts, so a dark-mode user saw a white page for the length of the bundle download. Reads the same `sm.theme` key `theme.ts` writes and applies the same rule; `theme.ts` stays the source of truth for every - later change, including the OS flipping at sunset. Inline is allowed - here — the CSP in `middleware.py` carries 'unsafe-inline' for scripts - (Vite's preamble needs it); tightening it to a nonce is a change to make - there and here together. #} -