slackhq / csp-html-webpack-plugin

A plugin which, when combined with HTMLWebpackPlugin, adds CSP tags to the HTML output.
MIT License
164 stars 40 forks source link
csp html-webpack-plugin webpack

CSP HTML Webpack Plugin

License npm Code Style Build Status codecov

About

This plugin will generate meta content for your Content Security Policy tag and input the correct data into your HTML template, generated by html-webpack-plugin.

All inline JS and CSS will be hashed and inserted into the policy.

Installation

Install the plugin with npm:

npm i --save-dev csp-html-webpack-plugin

Basic Usage

Include the following in your webpack config:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin');

module.exports = {
  // rest of webpack config

  plugins: [
    new HtmlWebpackPlugin()
    new CspHtmlWebpackPlugin({
      // config here, see below
    })
  ]
}

Recommended Configuration

By default, the csp-html-webpack-plugin has a very lax policy. You should configure it for your needs.

A good starting policy would be the following:

new CspHtmlWebpackPlugin({
  'script-src': '',
  'style-src': ''
});

Although we're configuring script-src and style-src to be blank, the CSP plugin will scan your HTML generated in html-webpack-plugin for external/inline script and style tags, and will add the appropriate hashes and nonces to your CSP policy. This configuration will also add a base-uri and object-src entry that exist in the default policy:

<meta http-equiv="Content-Security-Policy" content="
  base-uri 'self';
  object-src 'none';
  script-src 'sha256-0Tumwf1AbPDHZO4kdvXUd4c5PiHwt55hre+RDxj9O3Q='
             'nonce-hOlyTAhW5QI5p+rv9VUPZg==';
  style-src 'sha256-zfLUTOi9wwJktpDIoBZQecK4DNIVxW8Tl0cadROvQgo='
">

This configuration should work for most use cases, and will provide a strong layer of extra security.

All Configuration Options

CspHtmlWebpackPlugin

This CspHtmlWebpackPlugin accepts 2 params with the following structure:

HtmlWebpackPlugin

The plugin also adds a new config option onto each HtmlWebpackPlugin instance:

Order of Precedence:

You don't have to include the same policy / hashEnabled / nonceEnabled configuration object in both HtmlWebpackPlugin and CspHtmlWebpackPlugin.

In the case where a config object is defined in multiple places, it will be merged in the order defined below, with former keys overriding latter. This means entries for a specific rule will not be merged; they will be replaced.

> HtmlWebpackPlugin cspPlugin.policy
> CspHtmlWebpackPlugin policy
> CspHtmlWebpackPlugin defaultPolicy

Appendix

Default Policy:

{
  'base-uri': "'self'",
  'object-src': "'none'",
  'script-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"],
  'style-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"]
};

Default Additional Options:

{
  enabled: true
  hashingMethod: 'sha256',
  hashEnabled: {
    'script-src': true,
    'style-src': true
  },
  nonceEnabled: {
    'script-src': true,
    'style-src': true
  },
  processFn: defaultProcessFn
}

Full Default Configuration:

new HtmlWebpackPlugin({
  cspPlugin: {
    enabled: true,
    policy: {
      'base-uri': "'self'",
      'object-src': "'none'",
      'script-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"],
      'style-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"]
    },
    hashEnabled: {
      'script-src': true,
      'style-src': true
    },
    nonceEnabled: {
      'script-src': true,
      'style-src': true
    },
    processFn: defaultProcessFn  // defined in the plugin itself
  }
});

new CspHtmlWebpackPlugin({
  'base-uri': "'self'",
  'object-src': "'none'",
  'script-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"],
  'style-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"]
}, {
  enabled: true,
  hashingMethod: 'sha256',
  hashEnabled: {
    'script-src': true,
    'style-src': true
  },
  nonceEnabled: {
    'script-src': true,
    'style-src': true
  },
  processFn: defaultProcessFn  // defined in the plugin itself
})

Advanced Usage

Generating a file containing the CSP directives

Some specific directives require the CSP to be sent to the client via a response header (e.g. report-uri and report-to) You can set your own processFn callback to make this happen.

nginx

In your webpack config:

const RawSource = require('webpack-sources').RawSource;

function generateNginxHeaderFile(
  builtPolicy,
  _htmlPluginData,
  _obj,
  compilation
) {
  const header =
    'add_header Content-Security-Policy "' +
    builtPolicy +
    '; report-uri /csp-report/ ";';
  compilation.emitAsset('nginx-csp-header.conf', new RawSource(header));
}

module.exports = {
  {...},
  plugins: [
    new CspHtmlWebpackPlugin(
      {...}, {
      processFn: generateNginxHeaderFile
    })
  ]
};

In your nginx config:

location / {
  ...
  include /path/to/webpack/output/nginx-csp-header.conf
}

Contribution

Contributions are most welcome! Please see the included contributing file for more information.

License

This project is licensed under MIT. Please see the included license file for more information.