Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add __webpack__nonce to style tags #887

Closed
johannesnagl opened this issue Jun 8, 2017 · 34 comments
Closed

Add __webpack__nonce to style tags #887

johannesnagl opened this issue Jun 8, 2017 · 34 comments

Comments

@johannesnagl
Copy link

I already had a brief talk with @mxstbr about this a couple of weeks ago: https://twitter.com/mxstbr/status/824626370760364032

When trying to enforce strict rules about which content is allowed on a specific page, a Content Security Policy (CSP) is the way to go.

Styled-Components currently put all of their Styles as an inline style to the DOM. Therefore, a CSP would need to contain: style-src unsafe-inline. As the name already puts: This is unsafe and not the recommended way of securing your app.

Luckily, there is the concept of nonce attributes. Style/Script tags with a whitelisted nonce can be specified in the CSP. If Styled-Components would set this nonce, we could get rid of the unsafe-inline setting.

Luckily, WebPack is already supporting the concept of a global __webpack_nonce__ variable. Once set, all dynamically injected code created by WebPack will have a nonce attribute with the correct value.

What needs Styled-Components to do?

If the webpack_nonce variable is set, pass the value to every generated style tag inside the nonce attribute of the style tag.

Unluckily, the __webpack_nonce__ is not well documented and kind of a myth. See Google https://www.google.at/search?q=webpack_nonce__&oq=webpack_nonce__&aqs=chrome..69i57.4663j0j7&sourceid=chrome&ie=UTF-8#q=__webpack_nonce__ and GitHub: https://github.com/webpack/webpack/search?utf8=%E2%9C%93&q=__webpack_nonce__&type=

Further readings:

@mxstbr
Copy link
Member

mxstbr commented Jun 8, 2017

This seems like a very reasonable thing to implement!

@sokra @TheLarkInn can you confirm that __webpack_nonce__ will be supported in the future? Can you point us to any docs about it/is it documented at all?

@sokra
Copy link

sokra commented Jun 8, 2017

I guess there are no docs for it yet. But if you add docs for it it probably stays supported.

Here is the PR for the feature: webpack/webpack#3210

@arahansen
Copy link

Unfortunately, nonce attributes are not supported in ie11

Is there any known workaround for avoiding such CSP violations as this in ie11?

@elzii
Copy link

elzii commented Sep 27, 2017

I would love more discussion on this. PCI Compliance audits can mandate the need to have a CSP that prohibits using awesome libraries like this, or worse, having to remove them to pass compliance :-/

@arahansen
Copy link

@elzii The discussion here resolved with the fact that ie 11 doesn't support CSP restrictions anyways. So even if your server restricts inline styles, ie11 will still render them, and you will not see any errors in the browser.

@mindhaq
Copy link

mindhaq commented Feb 1, 2018

I would also love to see more discussion on this. My current setup with a strict Content-Security-Policy effectively prevents the use of styled components.

Inserting a static nonce with webpack seems like a bad idea as well. If all style elements have the same nonce everywhere everytime, this would defeat the whole purpose of having a nonce.

@mxstbr
Copy link
Member

mxstbr commented Feb 1, 2018

We implemented support for __webpack-nonce__ back in July, what else are you looking for @mindhaq?

@johannesnagl
Copy link
Author

johannesnagl commented Feb 1, 2018 via email

@simonpkerr
Copy link

@johannesnagl can you point me at that working solution? I can't get this working.
I've added webpack_nonce to my entry point index.js like this...

const styleTag = document.getElementsByTagName('style')[0] || {};
__webpack_nonce__ = styleTag.nonce || '';

this means the nonce generated on the server and injected into the template can be picked up by the react app entry point. The nonce is getting picked up from the style tag, but webpack isn't picking up the webpack_nonce value.

@johannesnagl
Copy link
Author

This is our implementation!

/* global __webpack_nonce__ */ // eslint-disable-line no-unused-vars

// CSP: Set a special variable to add `nonce` attributes to all styles/script tags
// See https://github.com/webpack/webpack/pull/3210
__webpack_nonce__ = window.NONCE_ID; // eslint-disable-line no-global-assign, camelcase

In our layout file, which is served via PHP, we have the following, simplified code (above the script tag with the webpack-bundle of course):

<script nonce="<?= $currentNonce ?>">window.NONCE_ID = '<?= $currentNonce ?>';</script>
<script src="HERE_COMES_YOUR_BUNDLE.js"></script>

@simonpkerr
Copy link

that looks pretty similar to what I've got. Does the webpack_nonce value need to be set before any imports? I'm getting an error.
The nonce shown in the image below is the one in the entry point file that references __webpack_nonce__. So its getting picked up in the entry file.

screen shot 2018-03-27 at 12 14 03

@mxstbr
Copy link
Member

mxstbr commented Mar 27, 2018

I think it has to be before any imports. Since imports are hoisted, you'll have to create a separate file (import-nonce.js) and import that before anything else that imports styled-components.

@simonpkerr
Copy link

thanks @mxstbr, that did it 👍

@johannesnagl
Copy link
Author

@simonkerr would you like to share the full setup with everyone, so that the next dev coming here has a better time then you had? #developerlove

@simonpkerr
Copy link

@johannesnagl where would you suggest the best place to do that? I'm writing a blog post at the moment about all this stuff and was thinking of updating the webpack docs for nonces.

@johannesnagl
Copy link
Author

@simonpkerr Fixing the nonce docs is definitely needed - so +1 for this!

Regarding my comment: I would just paste a small code example here. Nevertheless, if you write a blog post, feel free to add the link here too!

@simonpkerr
Copy link

So...in your server-side code you set up the nonce and expose it to your client-side code before any other scripts are defined

<script nonce="<?= $currentNonce ?>">window.NONCE_ID = '<?= $currentNonce ?>';</script>
<script src="HERE_COMES_YOUR_BUNDLE.js"></script>

In your entry point file, import your 'create-nonce.js' file before any files that reference styled components.

// (index.js)
import React from 'react';
import { Provider } from 'react-redux';
import { render, unmountComponentAtNode } from 'react-dom';
import { ThemeProvider } from 'styled-components';
import '../create-nonce';

import theme from './css/themes/default/theme';
import './css/default/baseline';
//...etc

Then in 'create-nonce.js' grab the global NONCE_ID variable...

__webpack_nonce__ = window.NONCE_ID;

Webpack will now add the nonce attribute to any injected script or style tag on a per request basis.

@isaacOstler
Copy link

@simonpkerr
Wouldn't setting the nonce to a global variable that can be accessed by any scripts loaded in the DOM defeat the purpose of having a nonce?

@tchryssos
Copy link

@isaacOstler My (possibly incorrect) understanding is that the current nonce is always available in the content security policy header (e.g. Content-Security-Policy: script-src 'nonce-87cFRrgNN7...'). The rub is that the attacker needs to know the next nonce, which should be impossible to guess. Since nonces change with every request, knowing the current nonce won't help you when XSS attacks are based on malicious requests.

@TheoMer
Copy link

TheoMer commented Jun 8, 2019

@johannesnagl Are there any examples of this working with next.js? I am absolutely stuck at #887 (comment)

@johannesnagl
Copy link
Author

@TheoMer sorry, don't have any experience with next.js!

@TheoMer
Copy link

TheoMer commented Jul 15, 2019

@mxstbr Any ideas on implementing this with next.js?

@sebastiaandegeus
Copy link

I still cannot get this to work. I have the following html:

<style nonce="w51UFM0uev/LhCA+J2h7Bw==" data-styled="" data-styled-version="4.3.2">
/* sc-component-id: sc-global-140695928 */

And the following header:

Content-Security-Policy: style-src 'nonce-w51UFM0uev/LhCA+J2h7Bw=='; default-src 'self'

I will get a lot of these errors:

StyleTags.js:119 Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'nonce-w51UFM0uev/LhCA+J2h7Bw=='". Either the 'unsafe-inline' keyword, a hash ('sha256-Lji2wCgIkBsj3rh83vXl1cGyRbsksFZ2OLHIzTwTYSc='), or a nonce ('nonce-...') is required to enable inline execution.

I don't understand why it is not working because the nonces are the same. It only works when I use 'unsafe-inline' for styleSrc.

@sebastiaandegeus
Copy link

sebastiaandegeus commented Oct 1, 2019

I managed to solve my problem by adding this to the html:

Notice the nonce on the script tag as well. You may need this if you also used a csp directive that disallows inline scripts.

<script nonce="w51UFM0uev/LhCA+J2h7Bw==">window.__webpack_nonce__ = "w51UFM0uev/LhCA+J2h7Bw==";</script>

@DylanPiercey
Copy link

@sebastiaandegeus doesn't the above negate the purpose of the nonce? Any scripts / styles injected client side would probably be able to read window.__webpack_none__ in the above right?

@sebastiaandegeus
Copy link

@DylanPiercey

I am not 100% sure, this is my understanding of it:

It seems that stealing the nonce is already a point of discussion in the spec:
w3c/webappsec-csp#65
w3c/webappsec-csp#98

As far as I understand injected scripts would not be executed if the nonce is not set. For example a comment in your comment section that contains a script tag. It will not be executed if the nonce is wrong and it cannot get the nonce because it is blocked right away.
If the attacker already has a way of executing their javascript code to steal the nonce then it's already too late. Blocking it will not help anymore.

I was digging through the code and found this: https://github.com/styled-components/styled-components/blob/147b0e9a1f10786551b13fd27452fcd5c678d5e0/packages/styled-components/src/utils/nonce.js

Which is used here to add a nonce to the stylesheet:

This is happening client side so it has to get the nonce from somewhere, which at that point had to be made available in a javascript variable or DOM.

Maybe one of the maintainers can shed some light on this issue?
If I implemented it wrong and/or insecure please let us know how to do it correctly.

@DylanPiercey
Copy link

@sebastiaandegeus yeah I think it makes sense. In order to read the nonce from the global variable the page would have already have to have been exploited.

@wzijden
Copy link

wzijden commented Apr 28, 2020

Could you at least add a section to your docs about the caveats of this library in combination with a strict CSP? I had to search and read for hours and I still don't completely get what is supported and what isn't. It now seems to me you can only get it working with server side rendering. Is that correct?

@theKashey
Copy link
Contributor

@wzijden - it all depends on your nonce settings. But it short - in a strict mode you have to provide nonce and SSR is often needed to do so. I mean - how without SSR you will generate and provide nonce?
However styled-components themselves are not bound to SSR. It's all about pure HTML.

And question - why one is actually need CSP for styles?
CSP for scripts or frames is actually a really good idea, but styles? You Aren't Gonna Need It.

@sasha-3ap
Copy link

sasha-3ap commented May 28, 2020

how without SSR you will generate and provide nonce?

For example, we use nginx and inject nonce in CSP headers and meta tag with property csp-nonce which is then used by material-ui.

@fdev
Copy link

fdev commented Nov 25, 2020

To the next person trying to get this to work: the code suggested by @johannesnagl in #887 (comment) and @simonpkerr in #887 (comment) does work, but not in development.

@gordonkristan
Copy link

gordonkristan commented Mar 2, 2021

This may be an odd case that only applies to us, but I want to point out how I got it working. The styled components code to get the nonce normally looks like this:

var getNonce = function getNonce() {
  return typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;
};

But after looking at the real source and not just the sourcemap, I saw this:

var getNonce = function getNonce() {
  return  true ? __webpack_require__.nc : undefined;
};

After some digging, it seems that webpack will replace references to __webpack_nonce__ with __webpack_require__.nc. I'm not 100% sure why that is and I don't want to dive into too much detail, but I can say that adding this code to the top of your first module will fix the issue:

__webpack_require__.nc = window.__webpack_nonce__;

Remember that it has to be in a module, because __webpack_require__ isn't available outside of a module.

EDIT: I want to point out that this is the same solution that simonpkerr demonstrated. The difference is that we want to set window.__webpack_nonce__ outside of a module. In our case, our PHP code is setting the nonce value. If you want to set the nonce value from inside a module then the code that simonpkerr posted will work.

@ljacho
Copy link

ljacho commented Mar 11, 2021

@gordonkristan can you elaborate on this please? can you bring example similar like @simonpkerr

thank you for your help!

@alex-r89
Copy link

Sorry to comment on a closed issue, but has anyone had any luck using this with NextJS and styled-components? I am getting Refused to apply inline styles errors when using styled-components with my content-security-policy.

My solution so far is listed below, but the nonce does not appear to be working.

NextJS + styled-components nonce issue
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'
import { randomBytes } from 'crypto'

const prod = process.env.NODE_ENV === 'production'

const inlineScript = (body, nonce) => (
<script type='text/javascript' dangerouslySetInnerHTML={{ __html: body }} nonce={nonce} />
)

function getCsp(nonce) {
let csp = ``
if (prod) {
  csp += `default-src 'self' 'nonce-${nonce}' data: https://*.myDomain.net https://*.amazonaws.com https://amazonaws.com;`
  csp += `script-src 'self' 'self' 'nonce-${nonce}' data: https://*.myDomain.net https://*.amazonaws.com https://amazonaws.com;`
  csp += `style-src 'self' 'nonce-${nonce}' data: https://*.myDomain.net;`
}

return csp
}

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
  const sheet = new ServerStyleSheet()
  const originalRenderPage = ctx.renderPage
  const nonce = randomBytes(8).toString('base64')
  const csp = getCsp(nonce)
  const res = ctx?.res
  if (res != null) {
    res.setHeader('Content-Security-Policy', csp)
  }

  try {
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />)
      })

    const initialProps = await Document.getInitialProps(ctx)

    return {
      ...initialProps,
      nonce,
      styles: (
        <>
          {initialProps.styles}
          {sheet.getStyleElement()}
        </>
      )
    }
  } finally {
    sheet.seal()
  }
}

render() {
  const { styles, nonce } = this.props

  return (
    <Html lang='en' dir='ltr'>
      <Head nonce={nonce}>
        {inlineScript(`window.__webpack_nonce__="${nonce}"`, nonce)}
        {styles}
      </Head>
      <body>
        <Main />
        <NextScript nonce={nonce} />
      </body>
    </Html>
  )
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests