next.js: Inline styles in Next 10 don't work with strict Content-Security-Policy

Bug report

Describe the bug

Recent versions of Next are using inline styles, which break our apps because we block style-src: unsafe-inline in our Content-Security-Policy header. In fact, the default behaviour of any CSP is to block unsafe inline styles. While the risk may be less than with unsafe inline scripts, there is still a risk: see this StackOverflow answer.

We have extremely high security requirements at my company so it’s not really a matter of easily being able to disable unsafe-inline, and I’m aware that the future intention is to move more of the styling inline. If we can’t upgrade Next because of this, we’ll either be stuck on an old version and potentially vulnerable that way, or we’ll have to consider alternatives (which I really don’t want to do!).

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Create a Content-Security-Policy header in next.config.js with style-src: 'self'; (i.e. without unsafe-inline explicitly enabled)
  2. Run the app in production mode
  3. See error in console:
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self'". Either the 'unsafe-inline' keyword, a hash, or a nonce ('nonce-...') is required to enable inline execution.

Expected behavior

If inline styles can’t be disabled, there should be the ability to add a nonce. This nonce will need to be regenerated on every request, and be able to be injected into the content-security-policy header.

I can see that the Head component in next/document accepts a nonce prop but this doesn’t appear to apply to Next-generated inline styles. Additionally, when trying to set the nonce prop I found it was being set on the link elements as a blank attribute - the actual nonce value was not being passed in, despite it being present in this.props.nonce (I tested with some console.logging in the compiled Next code). image

System information

  • Version of Next.js: [e.g. 6.0.2] 10.0.0

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 18
  • Comments: 18 (9 by maintainers)

Commits related to this issue

Most upvoted comments

I’m looking into this problem at the moment and I get the feeling that the solution is not appropriate. I don’t consider myself an expert on this, so please correct me if I’m wrong. So here’s the problem:

For a solution based on nonce it is essential that the value is randomly generated for every single request.

This means:

  1. The solution proposed above, generating the nonce value in the render() method of _document, does not work for statically rendered HTML pages as the nonce value will be generated once at build time and then remains the same until it is built again.
  2. Even if one were to disable static rendering in Next.js completely (which would be pretty sad), server-side caching would also have to be disabled, i.e. every page really needs to be rendered for every single request.

If I’m not missing anything here, this pretty much defeats one of the main objectives of Next.js, which is to make page loads as fast as possible.

As I said earlier today here, I don’t know the background of why the way CSS is now being loaded/pre-loaded has been changed recently. From what I can tell, everything worked fine until at least version 9.4, without the need for fetch-ing CSS and injecting styles.

Tiny speed improvements should not stand above best-practice security measures. Like I said, I don’t know the background is, but I think more needs to be done here. At the moment, if I want to stay on version 10, I have no choice but to use a style-src: 'unsafe-inline' CSP.

I don’t think the OP’s issue has been fixed here… next/image still outputs inline styles which means that you still have to use unsafe-inline for style-src

@Timer does Styled-JSX support nonce- stuff? I still get warnings with this apporach (without unsafe-inline), most probably caused by Styled-JSX.

The solutions proposed seem to work with the scripts injected by next, also with Material-UI, but I’m using CSS Modules, and none of my style tags have the nonce (next v11):

image

@sophiekoonin While we’re working on official (fully managed) CSP control within Next.js itself, we fixed the ability for you to pass nonce to <Head> in _document and have Next.js use it!

This should allow you to generate a valid CSP. You probably need to move from a headers approach to a <meta /> tag though if possible (so you can randomize it each request).

This doesn’t work with material-ui because the style tag doesn’t have the nonce. Any idea on how to add it?

The Next.js example with material-ui injects the styles in _document.js like follows:

MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);
  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
  };
};

You’re right @Manc, as Lukas Weichselbaum from web.dev said in a recent post, nonce-based CSP only works if the number is not guessable and newly generated at runtime for every response.

That’s why I’ve come to build the next-strict-csp package on NPM to implement a hash-based CSP with Next.js the right way.

Enjoy!

Something along these lines, copying the above code:

import NextDocument, { Html, Head, Main, NextScript } from "next/document";
import { randomBytes } from "crypto";

let prod = process.env.NODE_ENV == "production";

function getCsp(nonce) {
  let csp = ``;
  csp += `base-uri 'self';`;
  csp += `form-action 'self';`;
  csp += `default-src 'self';`;
  csp += `script-src 'self' ${prod ? "" : "'unsafe-eval'"};`; // NextJS requires 'unsafe-eval' in dev (faster source maps)
  csp += `style-src 'self' https://fonts.googleapis.com 'nonce-${nonce}' data:;`; // NextJS requires 'unsafe-inline'
  csp += `img-src 'self' https://*.githubusercontent.com https://paqmind.imfast.io data: blob:;`;
  csp += `font-src 'self' https://fonts.gstatic.com;`; // TODO
  csp += `frame-src *;`; // TODO
  csp += `media-src *;`; // TODO
  return csp;
}

// require-trusted-types-for 'script';" TODO

let referrer = "strict-origin";

export default class Document extends NextDocument {
  render() {
    // Regenerated every render:
    const nonce = crypto.randomBytes(8).toString("base64");
    return (
      <Html prefix="og: http://ogp.me/ns#" lang="ru">
        <Head nonce={nonce}>
          <meta httpEquiv="Content-Security-Policy" content={getCsp(nonce)} />
          <meta name="referrer" content={referrer} />
        </Head>
        <body>
          <Main />
          <div id="modal" />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Additionally, when trying to set the nonce prop I found it was being set on the link elements as a blank attribute - the actual nonce value was not being passed in, despite it being present in this.props.nonce

@sophiekoonin it’s not blank, just not visible to you via dev tools (see this answer on SO: https://stackoverflow.com/a/55673767/5805244).

I don’t think the OP’s issue has been fixed here… next/image still outputs inline styles which means that you still have to use unsafe-inline for style-src

Looks like the issue was solved and merged a couple weeks before this comment: https://github.com/vercel/next.js/pull/19150

nonce attribute is pulled from the head tag and applied to style tags that are later generated and inserted.

we’re working on official (fully managed) CSP control within Next.js itself

@Timer This is great news! Is there an RFC or an issue I could subscribe to for updates?