slackhq / csp-html-webpack-plugin

A plugin which, when combined with HTMLWebpackPlugin, adds CSP tags to the HTML output.
MIT License
163 stars 39 forks source link

Problem in setting nonce when using a custom processFn #79

Closed KohanTorabi closed 3 years ago

KohanTorabi commented 3 years ago

Description

When I use a custom processFn to procces the built policy, nonces aren't set in HTML elements. It works fine when I remove my custom processFn from additional Options

What type of issue is this? (place an x in one of the [ ])


Reproducible in:

slackhq/csp-html-webpack-plugin version: 4.0.0


Here is my code:

const cspConfigPolicy = {
  "default-src": ["'self'"],
  "connect-src": [
    "'self'",
    REACT_APP_WS_BASE_URL,
    REACT_APP_API_BASE_URL + "/",
    REACT_APP_FIREBASE_REMOTE_CONFIG_PROXY_HOST,
    REACT_APP_DATADOG_LOGS_PROXY_HOST,
    REACT_APP_DATADOG_RUM_PROXY_HOST,
    ...REACT_APP_ALLOWED_CONNECT_SRC_URLS.split(","),
    "https://firebaseinstallations.googleapis.com",
    "https://fcmregistrations.googleapis.com",
    "https://sentry.io",
    "https://www.google-analytics.com/",
  ],
  "base-uri": "'self'",
  "script-src": [
    "'self'",
    "https://www.google.com/recaptcha/",
    "https://use.fontawesome.com",
    "https://www.googletagmanager.com",
    "https://static.hotjar.com",
    "https://storage.googleapis.com/workbox-cdn/",
    "https://www.gstatic.com/",
    "'unsafe-inline'",
    ...REACT_APP_ALLOWED_SCRIPT_SRC_URLS.split(","),
  ],
  "style-src-elem": [
    "'self'",
    "'unsafe-inline'",
    "https://fonts.googleapis.com/css",
  ],
  "frame-src": [
    "'self'",
    "https://www.google.com/recaptcha/",
    ...REACT_APP_ALLOWED_FRAME_SRC_URLS.split(","),
  ],
  "font-src": ["'self'", "https://fonts.gstatic.com/"],
  "img-src": ["'self'", "data:"],
};

/**
 * Used to write the built csp in a file in the build directory
 * @param {string} builtPolicy
 */
const processBuiltCsp = (builtPolicy) => {
  fs.writeFile("./build/csp.txt", builtPolicy, (err) => {
    if (err) {
      return;
    }
  });
};

function addCspHtmlWebpackPlugin(config) {
  if (NODE_ENV === "production" && REACT_APP_CSP_ENABLE === "1") {
    config.plugins.push(
      new cspHtmlWebpackPlugin(cspConfigPolicy, {
        processFn: processBuiltCsp,
      })
    );
  }
  return config;
}
AnujRNair commented 3 years ago

This is expected - you can see that the default processFn is responsible for writing all HTML changes made by this plugin: https://github.com/slackhq/csp-html-webpack-plugin/blob/master/plugin.js#L44-L47

To fix this, you should add the lines highlighted in the link above into your custom processFn function. Specifically, overwriting htmlPluginData.html with the new html from $.html()

tomtom94 commented 1 year ago

THANKS A LOT

Take a look at this and enjoy

const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin')
const RawSource = require('webpack-sources').RawSource
const crypto = require('crypto')
const cheerio = require('cheerio')
const get = require('lodash.get')

const webpackNonce = crypto.randomBytes(16).toString('base64')

const externalUrls = [
  'https://fonts.googleapis.com',
  'https://www.googletagmanager.com',
]

function generateServeHeaderFile(builtPolicy, _htmlPluginData, $, compilation) {
  let metaTag = $('meta[http-equiv="Content-Security-Policy"]')

  // Add element if it doesn't exist.
  if (!metaTag.length) {
    metaTag = cheerio.load('<meta http-equiv="Content-Security-Policy">')(
      'meta'
    )
    metaTag.prependTo($('head'))
  }

  // build the policy into the context attr of the csp meta tag
  metaTag.attr('content', builtPolicy)
  // https://github.com/slackhq/csp-html-webpack-plugin/issues/79#issuecomment-752296938
  _htmlPluginData.html = get(_htmlPluginData, 'plugin.options.xhtml', false)
    ? $.xml()
    : $.html()

  const header = `{
  "headers": [
    {
      "source": "/**/**",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "${builtPolicy}"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "accelerometer=(self), ambient-light-sensor=(self), autoplay=(self), battery=(self), camera=(self), cross-origin-isolated=(self), display-capture=(self), document-domain=(self), encrypted-media=(self), execution-while-not-rendered=(self), execution-while-out-of-viewport=(self), fullscreen=(self), geolocation=(self), gyroscope=(self), keyboard-map=(self), magnetometer=(self), microphone=(self), midi=(self), navigation-override=(self), payment=(self), picture-in-picture=(self), publickey-credentials-get=(self), screen-wake-lock=(self), sync-xhr=(self), usb=(self), web-share=(self), xr-spatial-tracking=(self)"
        }
      ]
    }
  ]
}`
  compilation.emitAsset('../serve.json', new RawSource(header))
}

module.exports = [
  new CspHtmlWebpackPlugin(
    {
      'base-uri': "'self'",
      'object-src': "'self'",
      'script-src': [
        "'self'",
        "'unsafe-eval'",
        // "'unsafe-inline'",
        `'nonce-${webpackNonce}'`
      ].concat(externalUrls),
      'style-src': [
        "'self'",
        "'unsafe-inline'"
        // `'nonce-${webpackNonce}'`
      ].concat(externalUrls)
    },
    {
      enabled: true,
      hashingMethod: 'sha256',
      hashEnabled: {
        'script-src': false,
        'style-src': false
      },
      nonceEnabled: {
        'script-src': true,
        'style-src': false
      },
      processFn: generateServeHeaderFile
    }
  )
]