Browser-sync: CSP support via nonce

Created on 7 Jun 2017  Â·  4Comments  Â·  Source: BrowserSync/browser-sync

Issue details

At the moment in order to make browsersync work on site with CSP which disables usage of inline scripts I have to generate sha hash (I used following script: https://gist.github.com/WebReflection/892939bcf8272bf4791f). It would be nice if I could provide nonce instead. That will insure that if I upgrade browsersync, I don't have to generate new sha.

If you're not familiar with CSP, you can find details here: https://content-security-policy.com/

Steps to reproduce/test case

Try to run browsersync which proxies site with CSP which sets script-src to 'self'. Browsersync will not initialize unless you specify sha hash.

This problem is not specific to node version or OS, but you'll need to use browser that supports CSP - Chrome should be good for test.

Browsersync use-case

  • [x ] Gulp

for all other use-cases, (gulp, grunt etc), please show us exactly how you're using Browsersync

//gulp browsersync config
  browserSync.init(null, {
    proxy: `https://localhost:${appPort}`,
    files: [
      'public/dist/css/*.css',
      'views/**/*.*'
    ],
    browser: "chrome",
    open: "external",
    https: {
      key: path.join(__dirname, './ssl/upload.indigo.ca.key'),
      cert: path.join(__dirname, './ssl/upload.indigo.ca.crt')
    },
    host: 'site.com',
    port: 443,
  });

//code that sets CSP
app.use(helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: [
        "'self'",
        function (req, res) {
          return `'nonce-${res.locals.nonce}'`
        }
      ],
      styleSrc: ["'self'"],
      upgradeInsecureRequests: true
    }
}));

Thank you

Most helpful comment

Second option about BS computing correct hash sounds like an easier and straight forward solution.

All 4 comments

I'm not sure exactly using a nonce would work in this situation. The Nonce needs to be generated by your actual backend serving the HTML and be different for every request - Perhaps BrowserSync would be able to read the nonce value from the headers/elsewhere and use it in the corresponding nonce attribute on the injected <script> tag.

Alternatively BrowserSync could just compute the correct hash value for the content of the injected script, and include that in the injected <script> tag?

Second option about BS computing correct hash sounds like an easier and straight forward solution.

I came back to this issue and there is a workaround. In options add something like:

        snippetOptions: {
            rule: {
                match: /<\/head>/i,
                fn: function (snippet, match) {
                    return snippet.replace('id=', `nonce="browser-sync-${os.hostname()}" id=`) + match;
                }
            }
        },

and in place where you configure CSP headers add something like:

scriptSrc: self
                .concat((req, res) => `'nonce-${res.locals.nonce}'`)
                .concat(isDev ? [`'nonce-browser-sync-${os.hostname()}'`] : [])

Hope you got the idea. With this I'm closing the issue and if someone wants a better approach, I suggest to start another issue or create a PR. Thanks.

@webuniverseio 's answer still valid in 2020. Although I changed

nonce="browser-sync-${os.hostname()}"

for simply

nonce="browser-sync"

If you're using Apache's .htaccess you can set the CSP like this:

Header always set Content-Security-Policy "default-src 'self' whatever-you-want-here…"
Header always set Content-Security-Policy "script-src 'self' 'nonce-browser-sync' whatever-you-want-here…"
Was this page helpful?
0 / 5 - 0 ratings