netlify-plugin-csp-generator

Generate CSP headers from inline script hashes

Usage no npm install needed!

<script type="module">
  import netlifyPluginCspGenerator from 'https://cdn.skypack.dev/netlify-plugin-csp-generator';
</script>

README

netlify-plugin-csp-generator

NPM codecov CodeQL

Generate Content-Security-Policy headers from inline script and style hashes

When running things like Gatsby or Gridsome, the initial state is stored inside a <script> tag. Modern browser content security policies don't like inline scripts or styles, so to get around it you need to add either a cryptographic nonce or a cryptographic hash of each script. A nonce is out of the question, because you can't update it for each load.

This package generates a crypographic hash (SHA-256) of all inline scripts and styles in each HTML file, and adds it to the _headers file along with other policies of your choice.

Note
Netlify lets you add a Content-Security-Policy header in your netlify.toml. This will overwrite values inside _headers, so don't do that.

If you have an existing _headers file, this will append to the existing file. Just make sure the file ends on a newline, and it should work fine.

Usage

Install netlify-plugin-csp-generator with your favourite package manager:

yarn add netlify-plugin-csp-generator

npm install netlify-plugin-csp-generator

In your netlify.toml file, add an additional plugin:

[[plugins]]
package = "netlify-plugin-csp-generator"

  [plugins.inputs]
  buildDir = "dist"

  [plugins.inputs.policies]
    defaultSrc = "'self'"

Properties

  • buildDir is the path for the publish directory in Netlify: buildDir example
  • exclude is an array of paths you don't want to include. It defaults to an empty array.
  • disablePolicies is an array of policies to never include. Files that need these rules will probably be taken from defaultSrc instead by your browser.
  • disableGeneratedPolicies is an array of policies never to generate. Use this to turn off default policies but still allow the key in netlify.toml.
  • reportOnly generates headers with Content-Security-Policy-Report-Only instead, which is useful for testing.
  • reportURI/reportTo sends violations to a given endpoint. See Reporting violations for more information.
  • generateForAllFiles lets you generate headers for non-HTML files. See Non-index.html files for more information.

Policies

You can use the following policies:

Add them under the [plugins.inputs.policies] object in your netlify.toml file, with your specified value in quotes.

You can use CSP headers not in this list too - simply use the name in camel case and it will be added.

Inline styles

When using Vue and derivatives (like Gridsome), you may want to use v-show on things. This adds an inline style of display: none;, which is forbidden by CSP Level 3. To prevent this throwing an error, you need to add 'unsafe-hashes' to your styleSrc policy. The sha-256 hash is generated automatically.

[[plugins]]
package = "netlify-plugin-csp-generator"

  [plugins.inputs]
  buildDir = "dist"

  [plugins.inputs.policies]
    defaultSrc = "'self'"
    styleSrc = "'unsafe-hashes'"

What is generated

If you have defined a policy in your netlify.toml file, this will be added to all files.

  [plugins.inputs.policies]
    defaultSrc = "'self'"
    scriptSrc = "'self' https://www.google-analytics.com https://ssl.google-analytics.com https://www.googletagmanager.com"
/each-file-path/
  Content-Security-Policy: default-src 'self'; script-src 'self' *.google-analytics.com;

If a file includes a <script> or <style> tag with content, this file path will have the hash added:

/file-with-no-script/
  Content-Security-Policy: default-src 'self';
/file-with-script/
  Content-Security-Policy: default-src 'self'; script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=';

If a file has any inline styles, these will be hashed:

<div style="display:none;"></div>
/file-with-inline-style/
  Content-Security-Policy: style-src 'unsafe-hashes' 'sha256-0EZqoz+oBhx7gF4nvY2bSqoGyy4zLjNF+SDQXGp/ZrY='

Non-index.html files

Generally, routes are generated with an index.html file, like /some/file/path/index.html. However, sometimes you need to handle HTML files that aren't called 'index', for example 404.html in Nuxt.

These are generated as wildcard links and are placed above the non-wildcard paths in your _headers file (for specificity):

/*.html
  Content-Security-Policy: default-src 'self'; script-src 'sha256-Qb2XxXiF09k6xbk2vTgHvWRed+mgYYGzFqZ6dShQVA0=';
/specific-path/
  Content-Security-Policy: default-src 'self';

Any matching wildcard URL has the hashes joined together - for example, if you have a 404.html and a 500.html with scripts/styles, all the hashes will be merged together under /*.html.

In general, it is better to generate /path/index.html rather than /path.html.

Using the generateForAllFiles setting, you can generate route keys that use /* instead of /*.html. Be careful, this will send Content-Security-Policy headers for every file type (i.e. .js, .css, etc) which is redundant as per the spec.

Reporting violations

The Content-Security-Policy specification allows for reporting violations to a URL - you can read more about it on MDN.

This is useful for testing and checking directives.

To set the header to report only, set reportOnly = true in your netlify.toml alongside your policies.

  [plugins.inputs]
  reportOnly = true
  reportURI = "/report-csp-violations-to-this-uri"

Important

  1. Setting reportOnly to true will NOT enforce your policy
  2. You need to add reportURI too

Using the report-to directive

The reportURI is deprecated in CSP Level 3 in favour of report-to. To use the report-to directive, set the reportTo value to the group name as defined in the Reporting-Endpoints header that you also need to set.

  [plugins.inputs]
  reportOnly = true
  reportTo = "csp-violations-group"
  1. You can include reportURI and reportTo without setting reportOnly = true, and the policy WILL be enforced and errors will also be reported
  2. You can set both the reportTo and reportURI directives - this is recommended to ensure maximum compatibility

Help it's all broken!

Oh, you. Chances are your browser console is screaming at you, and the network tab is showing a lot of (blocked:csp) errors.

See our list of example policies to get started.

Don't unsafe-inline everything, because that will make CSP redundant. If in doubt, ask Google, Stackoverflow, or create a Github issue (in that order).

Donations

If you found this plugin useful, or are just feeling nice, feel free to donate!

Buy me a coffee