Content
View differences
Updated by Alexander Coles 14 days ago
## Context
Two cross-cutting concerns in the Turbo↔Angular layer are each encoded in several places, with no single owner. Both are good consolidation (deepening) candidates and become easier once `frontend/src/turbo/` has a jsdom test seam (see #DREAM-514 / the testability work package).
## A) CSP nonce enforcement has one owner per render path instead of one owner
`scrubScriptElements` (the pure helper in `frontend/src/turbo/helpers.ts`) is unit-tested, but the security-critical behaviour lives in the wiring: it is hooked separately in three render paths in `frontend/src/turbo/turbo-event-listeners.ts` — `turbo:before-render` and `turbo:before-frame-render` (both `{ capture: true }`) and `turbo:before-stream-render` (which hand-wraps `event.detail.render`) — plus a fourth, separate `X-Turbo-Nonce` request header. A regression in any one hook (wrong phase, or a render path Turbo adds in a future version) silently lets unauthorised scripts through, and no test covers the hooks.
* Consolidate the three hook points and the header into one `enforceScriptNonce(target = document)` module that owns every render path.
* Add a jsdom spec: dispatch a synthetic `turbo:before-stream-render` carrying a bad-nonce `<script>` and assert it is removed — so the security guarantee, not the leaf helper, is the test surface.
## Notes
Relates to #DREAM-514. Part A has a security edge and is the higher-value half. The `turbo_power` allowlist in `setup.ts` (a related "imperative registration" smell) is being addressed upstream via https://github.com/marcoroth/turbo\_power/pull/333 and is intentionally out of scope here.
### Hints for QA
Changes are DX-focused. Does not need manual QA.
Two cross-cutting concerns in the Turbo↔Angular layer are each encoded in several places, with no single owner. Both are good consolidation (deepening) candidates and become easier once `frontend/src/turbo/` has a jsdom test seam (see #DREAM-514 / the testability work package).
## A) CSP nonce enforcement has one owner per render path instead of one owner
`scrubScriptElements` (the pure helper in `frontend/src/turbo/helpers.ts`) is unit-tested, but the security-critical behaviour lives in the wiring: it is hooked separately in three render paths in `frontend/src/turbo/turbo-event-listeners.ts` — `turbo:before-render` and `turbo:before-frame-render` (both `{ capture: true }`) and `turbo:before-stream-render` (which hand-wraps `event.detail.render`) — plus a fourth, separate `X-Turbo-Nonce` request header. A regression in any one hook (wrong phase, or a render path Turbo adds in a future version) silently lets unauthorised scripts through, and no test covers the hooks.
* Consolidate the three hook points and the header into one `enforceScriptNonce(target = document)` module that owns every render path.
* Add a jsdom spec: dispatch a synthetic `turbo:before-stream-render` carrying a bad-nonce `<script>` and assert it is removed — so the security guarantee, not the leaf helper, is the test surface.
## Notes
Relates to #DREAM-514. Part A has a security edge and is the higher-value half. The `turbo_power` allowlist in `setup.ts` (a related "imperative registration" smell) is being addressed upstream via https://github.com/marcoroth/turbo\_power/pull/333 and is intentionally out of scope here.
### Hints for QA
Changes are DX-focused. Does not need manual QA.