Skip to content

Content Security Policy (CSP)

CSP adds an extra layer of security to your app. For statically rendered apps, a script-src policy can be automatically generated for you. SSR apps have an extra step.

NOTE

This feature is not available to libraries

Setup

Set cspEnabled: true in your sku.config.js.

Delivery

The cspDelivery option controls how the CSP is delivered and can be set to one of two values:

  • tag: The CSP will be embedded directly in the rendered HTML content via a <meta http-equiv="Content-Security-Policy" …> tag. No further action will be required to enable the CSP. This is the default behaviour if no cspDelivery option is specified.
  • header: The CSP will be written to a JSON file alongside the rendered HTML content (e.g. index.html.json) in the metadata.csp property, and no <meta http-equiv="Content-Security-Policy" …> tag will be generated. Extra steps will be required at deployment and/or request time to ensure the value of this property is returned as a Content-Security-Policy header in the response for the rendered HTML content.

The cspDelivery option is only available when using Vite.

Extra Hosts

If you need to allow scripts that are only known client side (e.g. scripts loaded by tag managers) you can add their URLs to the cspExtraScriptSrcHosts array in sku.config.js.

Report To

The cspReportTo option allows reports of CSP violations to be captured via the browser Reporting API. This option can be configured either as an endpoint name, a URL, or as a tuple of both, with the following outcome:

  • If only an endpoint name is specified, then this value will be included in the CSP as the value of the report-to directive, and no Reporting-Endpoints header will be emitted.
  • If only a URL is specified, then an endpoint name will be generated automatically and included in the CSP as the value of the report-to directive, and a Reporting-Endpoints header will be emitted containing the generated endpoint name and the provided URL.
  • If both an endpoint name and a URL is specified, then the provided endpoint name will be included in the CSP as the value of the report-to directive, and a Reporting-Endpoints header will be emitted containing both the provided endpoint name and URL.

The cspReportTo option is only effective when using the header delivery option. If a Reporting-Endpoints header is emitted it will be written to same JSON file in the metadata.reportingEndpoints property.

Nonce Values

Nonce values can be used to permit inline scripts that are generated client side. Nonce values are created by calling createUnsafeNonce during render.

NOTE

The Content Security Policy (CSP) requires that scripts be declared ahead of time. For inline scripts this is typically done automatically by calculating a hash of their content when they are created during the initial render. This ensures only authorised scripts are run in client environments.

When a script is created dynamically on the client it may not be possible to predict the required hash, in this case a nonce can be used.

WARNING

Nonces are less safe than content hashes. Please consider if other options are available and whether the risks are acceptable for your use-case.

createUnsafeNonce: Generates a random nonce value and returns it for use by the client. The nonce value is added to the generated Content Security Policy (CSP) Tags.

Example: Using createUnsafeNonce to create a nonce value and use it client side

tsx
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 }) => {
    // ...
  },
};
tsx
import App from './App';

export default ({ dynamicScriptNonce }) => {
  client.init({ nonce: dynamicScriptNonce }); 
  // ...
};

Extra SSR Setup

As sku doesn't handle the returned HTML in SSR apps, any extra scripts (scripts not created by sku) must be registered.

In the renderCallback function, register all extra script tags (inline and external) via the registerScript function.

IMPORTANT

If you are using multi-part responses via the flushHeadTags API, all scripts must be registered before sending the the initial response.

tsx
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

A "report-only" Content Security Policy can be enabled by setting cspReportOnlyEnabled: true in your sku.config.js. This will cause a Content-Security-Policy-Report-Only header to be generated.

By default the report-only CSP will have the same content as the standard CSP, including the same extra hosts. This can be changed by setting the cspReportOnlyExtraScriptSrcHosts array in sku.config.js to contain the script URLs for the report-only CSP. Similarly, the report-only CSP will share the same reporting configuration as the standard CSP, and this also can be changed by setting the cspReportOnlyReportTo option.

Unlike the standard CSP, a report-only CSP can only be delivered via an HTTP header and not via a <meta http-equiv> tag. As such there is no explicit delivery option for a report-only CSP and the behaviour matches that of header CSP delivery, with the policy being written to the metadata.cspReportOnly property. As a consequence, and like the delivery option itself, a report-only CSP is only available when using Vite.

A report-only CSP can be enabled or disabled independently of the standard CSP, and vice versa.