Content Security Policy (CSP)
CSP adds a security policy to your app. For statically rendered apps, sku can generate a script-src policy for you automatically. SSR apps have an extra step.
NOTE
This feature is not available to libraries
Setup
Set cspEnabled: true in your sku.config.js.
Delivery
For static apps, the cspDelivery option controls how sku delivers the enforcing CSP. Set it to one of two values:
tag: sku embeds the CSP in the rendered HTML via a<meta http-equiv="Content-Security-Policy" …>tag. You do not need further action to enable the CSP. This is the default if you do not setcspDelivery.header: sku writes the CSP to a JSON file next to the rendered HTML (e.g.index.html.json) in themetadata.cspproperty. sku does not generate a<meta http-equiv="Content-Security-Policy" …>tag. You must take extra steps at deployment and/or request time. Those steps must return this property as aContent-Security-Policyheader on the response for the rendered HTML.
cspDelivery applies to Static Vite apps only (buildType unset / 'static'). SSR always emits real HTTP CSP headers and ignores cspDelivery.
Extra Hosts
If you need to allow scripts that are only known on the client, add their URLs to the cspExtraScriptSrcHosts array in your sku.config.js. This includes scripts loaded by tag managers.
Report To
The cspReportTo option lets the browser capture CSP violation reports via the Reporting API. You can set this option as an endpoint name, a URL, or a tuple of both. The outcome is:
- If you set only an endpoint name, sku includes that value in the CSP as the
report-todirective. sku does not emit aReporting-Endpointsheader. - If you set only a URL, sku generates an endpoint name automatically. sku includes that name in the CSP as the
report-todirective. sku emits aReporting-Endpointsheader with the generated endpoint name and the URL you provided. - If you set both an endpoint name and a URL, sku includes the provided endpoint name in the CSP as the
report-todirective. sku emits aReporting-Endpointsheader with both the provided endpoint name and URL.
For static apps, the cspReportTo option is only effective when you use the header delivery option. sku writes an emitted Reporting-Endpoints header to the same JSON file in the metadata.reportingEndpoints property.
Nonce Values
You can use Nonce values to permit inline scripts that the client generates.
NOTE
The Content Security Policy (CSP) requires that scripts be declared ahead of time. For inline scripts this is typically done automatically. A hash of their content is calculated when they are created during the initial render. This ensures client environments run only authorised scripts.
When the client creates a script dynamically it may not be possible to predict the required hash. In this case you can use a nonce.
WARNING
Nonces are less safe than content hashes. Consider whether other options are available. Consider whether the risks are acceptable for your use-case.
SSR - Managed Data Mode SSR experimental
SSR uses at most one CSP nonce per HTML response. sku mints a nonce only when explicitly requested. sku includes it in the CSP header only if requested.
The following request a nonce:
- sku itself, when attaching a
nonceto React stream scripts (post-shell inline scripts that cannot be pre-hashed) - Express middleware:
req.getCspNonce()(mint-on-read. Later calls return the same value) - React Router loaders/actions:
getCspNonce()fromsku/runtime(same store while sku is rendering)
All of those share one value for the response. Known bootstrap script bodies are still allowed via sha256 hashes.
A nonce is not available in client code. getCspNonce returns undefined if you call it from the browser. Isomorphic code can still call it safely.
Webpack SSR / static apps Webpack SSR
createUnsafeNonce: Generates a random nonce value and returns it for use by the client. sku adds the nonce value to the generated Content Security Policy (CSP) tags.
Example: Using createUnsafeNonce to create a nonce value and use it client side
export default {
renderApp: ({ createUnsafeNonce }) => {
const appHtml = renderToString(<App />);
const dynamicScriptNonce = createUnsafeNonce();
return { appHtml, dynamicScriptNonce };
},
provideClientContext: ({ environment, app }) => ({
environment,
dynamicScriptNonce: app.dynamicScriptNonce,
}),
renderDocument: ({ app, bodyTags, headTags }) => {
// ...
},
};import App from './App';
export default ({ dynamicScriptNonce }) => {
client.init({ nonce: dynamicScriptNonce });
// ...
};Extra SSR Setup (Webpack)
sku does not handle the returned HTML in Webpack SSR apps. You must register any extra scripts (scripts not created by sku).
In the renderCallback function, register all extra script tags (inline and external) via the registerScript function.
IMPORTANT
If you use multi-part responses via the flushHeadTags API, register all scripts before you send the initial response.
import type { Server } from 'sku';
const renderCallback: Server['renderCallback'] = (
{ SkuProvider, getHeadTags, getBodyTags, registerScript },
req,
res,
) => {
const someExternalScript = `<script src="https://code.jquery.com/jquery-3.5.0.slim.min.js"></script>`;
const someInlineScript = `<script>console.log('Hi');</script>`;
registerScript(someExternalScript);
registerScript(someInlineScript);
const app = renderToString(
<SkuProvider>
<App />
</SkuProvider>,
);
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Awesome Project</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
${getHeadTags()}
</head>
<body>
<div id="app">${app}</div>
${someInlineScript}
${getBodyTags()}
${someExternalScript}
</body>
</html>`);
};Report-only Content Security Policy
You can enable a "report-only" Content Security Policy by setting cspReportOnlyEnabled: true in your sku.config.js. This causes sku to generate a Content-Security-Policy-Report-Only header.
By default the report-only CSP has the same content as the standard CSP, including the same extra hosts. You can change this by setting the cspReportOnlyExtraScriptSrcHosts array in sku.config.js to the script URLs for the report-only CSP. The report-only CSP also shares the same reporting configuration as the standard CSP. You can change that by setting the cspReportOnlyReportTo option.
Unlike the standard CSP, a report-only CSP can only be delivered via an HTTP header, not via a <meta http-equiv> tag. There is no explicit delivery option for a report-only CSP. The behaviour matches header CSP delivery. sku writes the policy to the metadata.cspReportOnly property. As a consequence, a report-only CSP is only available when using Vite. The same limit applies to the delivery option itself.
A report-only CSP can be enabled or disabled independently of the standard CSP, and vice versa.
