Recipe for a single-page app's Content-Security-Policy.
spa_csp builds the Content-Security-Policy header value for a prerendered
SvelteKit SPA behind nginx. The
baseline is fixed and security-sensitive — script-src/style-src permit
'unsafe-inline' because adapter-static emits inline bootstrap scripts whose
content changes every build (a strict nonce/hash CSP would need SvelteKit's
build-time csp, not an nginx header), 'wasm-unsafe-eval' is for the blake3
WASM, and everything else is locked down. What varies per app is a handful of
extra sources (a site that uses blob: images + web workers), so those are
options defaulting to the locked-down baseline.
The value is consumed as the csp option of the site recipes
({debian,alpine}_nginx_spa_site), which interpolate it into the
add_header Content-Security-Policy "..." always; lines. connect-src carries the WS
endpoint as the zap template placeholder wss://{{domain}}, substituted at
apply time alongside the rest of the site config.
Recipes are imported at config eval time inside fgro eval's sandbox,
which has no network, no writes, and no environment, and where the clock and
randomness throw. This module is therefore a pure function: it only assembles a
string from its arguments — no I/O, no clock, no randomness, no imports beyond
types. The output is deterministic for given inputs, so it stays a reliable
content-addressed plan artifact.