Set a CSP
A Content-Security-Policy keeps a script that slipped into the content from running. In this mode you give the policy to the csp option of framework(), and the build writes it into each page’s <meta>.
On this page
What the framework signs
The framework decides no policy. It signs the inline scripts it writes into the HTML, and the policy that allows them is the application’s to write.
The inline scripts the framework writes are the payload it puts into the HTML for hydration, and React’s own.
An inline script the application writes itself is allowed by the policy the same way — @k8ordo/color-scheme’s, which sets the colours before the first paint, among them.
Give the policy to the csp option
A static file reads the same to everyone, so it cannot carry a nonce that changes per request. The build names the framework’s inline scripts by hash instead.
vite.config.tsimport { colorSchemeScriptHash } from '@k8ordo/color-scheme';
import { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
framework({
csp: {
'script-src': ["'self'", await colorSchemeScriptHash()],
'object-src': ["'none'"],
'base-uri': ["'none'"],
},
}),
],
});Give csp an array of sources per directive. Each page gets a <meta http-equiv="Content-Security-Policy"> first in its <head>, with the hashes of that page’s scripts added.
The hashes go into script-src. When only default-src was given, script-src is made from it first. When script-src-elem was given, they go there too. With none of them, scripts are not restricted, and nothing is added.
Without csp, no policy is written.
Allow the application’s own inline scripts
An inline script of the application’s own is allowed by writing its hash into the policy. For @k8ordo/color-scheme, colorSchemeScriptHash() returns it.
If ColorSchemeProvider is given a defaultPreference, pass the same value to colorSchemeScriptHash(): the script changes with it, and so does the hash.
Every inline script whose hash is not in the policy is refused, which is what keeps one that slipped in from the content from running.
What csp cannot hold
The framework’s module script cannot be signed in a file, so it is allowed by where it comes from, 'self'. 'strict-dynamic' makes 'self' ignored, so a policy that holds it is refused.
frame-ancestors, report-uri and sandbox are refused too: a <meta> ignores them, so set them as headers at the host.
Pitfall
The refusal comes as framework() is called: it throws the "csp" option cannot go into a page's <meta> as it is:, followed by one line per problem.