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

CSP nonce not being added to inline scripts #55638

Closed
1 task done
sleepdotexe opened this issue Sep 20, 2023 · 14 comments
Closed
1 task done

CSP nonce not being added to inline scripts #55638

sleepdotexe opened this issue Sep 20, 2023 · 14 comments
Labels
examples Issue/PR related to examples locked

Comments

@sleepdotexe
Copy link
Contributor

sleepdotexe commented Sep 20, 2023

EDIT: See this comment below for an important update.


Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: win32
      Arch: x64
      Version: Windows 10 Home
    Binaries:
      Node: 18.17.0
      npm: N/A
      Yarn: N/A
      pnpm: N/A
    Relevant Packages:
      next: 13.5.1-canary.1
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: N/A
    Next.js Config:
      output: N/A

Which example does this report relate to?

with-strict-csp

What browser are you using? (if relevant)

Chrome 116.0.5845.188 (Official Build) (64-bit)

How are you deploying your application? (if relevant)

Vercel, Nodejs 18

Describe the Bug

According to the docs and example, the correct way to implement a strict CSP with nonces is by using middleware. However, even with a clean install of the example, the nonce is not being added to scripts correctly.

The nonce property is appearing on <script> tags, however the value is always empty.

<body>
  <!-- ... -->
  <script src="/_next/static/chunks/webpack.js?v=1695191539001" nonce="" async=""></script>
  <script nonce="">(self.__next_f=self.__next_f||[]).push([0])</script>
  <!-- etc. -->
</body>

A custom next/script tag also does not have the nonce value added.

export default function RootLayout({ children }) {
  const nonce = headers().get("x-nonce");
  console.log(nonce);

  return (
    <html lang="en">
      <body>{children}</body>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX`}
        strategy="afterInteractive"
        nonce={nonce}
      />
    </html>
  );
}

gives us:

<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" nonce="" data-nscript="afterInteractive"></script>

If I try to show the nonce value on the page itself, it will display valid nonce value, so I don't think the error is with the middleware/nonce creation.

// app/page.js
import { headers } from "next/headers";

export default function Page() {
  const nonce = headers().get("x-nonce");
  console.log(nonce);

  return (
    <p>nonce: {nonce}</p>
  );
}

gives us:

<p>nonce: <!-- -->MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx</p>

Our CSP response header is also correctly showing the nonce:
default-src 'self'; script-src 'self' 'nonce-MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx' 'strict-dynamic' 'unsafe-eval';

Expected Behavior

Next's inline <script> tags (including ones created from <Script> components) should have the nonce value added.

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64') 
  //nonce = MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx

  //...

  requestHeaders.set('x-nonce', nonce);
  requestHeaders.set(
    'Content-Security-Policy',
    // Replace newline characters and spaces
    cspHeader.replace(/\s{2,}/g, ' ').trim()
  );

  return NextResponse.next({
    headers: requestHeaders,
    request: {
      headers: requestHeaders,
    },
  })
}
// app/layout.js
import { headers } from "next/headers";
import Script from "next/script";

export default function RootLayout({ children }) {
  const nonce = headers().get("x-nonce");
  console.log(nonce);

  return (
    <html lang="en">
      <body>{children}</body>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=G-AAAAAAAAAA`}
        strategy="afterInteractive"
        nonce={nonce}
      />
    </html>
  );
}

Should output:

<html lang="en">
    <head><!-- ... --></head>
    <body>
        <!-- ... -->
        <script src="/_next/static/chunks/webpack.js?v=1695191539001" nonce="MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx" async=""></script>
        <!-- ... -->
        <script src="https://www.googletagmanager.com/gtag/js?id=G-AAAAAAAAAA" nonce="MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx" data-nscript="afterInteractive"></script>
    </body>
</html>

To Reproduce

  • Install the example (with a command such as npx create-next-app --example with-strict-csp with-strict-csp-app)
  • Run the example (npm run dev)
  • Look at the HTML output in console and check <script> tags for the nonce value.
@sleepdotexe sleepdotexe added the examples Issue/PR related to examples label Sep 20, 2023
@fernandojbf

This comment was marked as spam.

@levipadre

This comment has been minimized.

@sleepdotexe
Copy link
Contributor Author

An update on this – did a bit more investigation and I think there's a bit of additional information that may be helpful

  1. Inline scripts are actually working, Chrome just hides the nonce in the browser console.
    For all of Next's inline scripts, it looks like the nonce is actually being correctly added, it just shows up as nonce="" in the browser console (presumably for security or readability measures). You can confirm the nonce has been added by selecting an element, and then selecting "Properties" from the Styles, Computed, Layout tabs.
image
  1. Part of the issue is due to Vercel's edge runtime.
    This behaviour happens in development (npm run dev) and local builds (npm run build, npm run start). The nonce attribute does not seem to be added at all when deployed to Vercel using the edge runtime export const runtime = 'edge'. The page fails to load correctly and all scripts, inline or 3rd party, are rejected by the CSP.

  2. next/script tags using afterInteractive appear to be initially rendered without the nonce, but then hydrated(?) with the nonce
    One issue currently is that <Script> components with a 3rd party js src are still logging errors to console, claiming that the script load was blocked due to CSP. From what I can tell, this appears to be due to the fact that the <script> tag is being created initially without the nonce, and then the nonce is added later, causing the script to be re-fetched. You can verify this in the browser devtools in the network tab - you should see the script being tried once with a CSP error and then a second time with a 200. For me, this is causing issues with analytics tools working properly.

Some possible temporary fixes include:

  • Disable edge runtime and use nodejs instead
  • Remove nonces from the CSP and use a domain allowlist + unsafe-inline for now

It'd be great to get some insight on why the nonce is not being initially added with the <script> tag when using afterInteractive.

@yelodevopsi
Copy link

Seem like there has been a change from yesterday (22.sept 2023) where the nonce is not added to the <script..> tags any longer.

I've reset and tested both node 16, 18 and 20 and all give the same errors with missing nonce value

Here's todays test with Chrome Version 117.0.5938.88 (Official Build) (arm64)

image

@sleepdotexe
Copy link
Contributor Author

Seem like there has been a change from yesterday (22.sept 2023) where the nonce is not added to the <script..> tags any longer.

I've reset and tested both node 16, 18 and 20 and all give the same errors with missing nonce value

Here's todays test with Chrome Version 117.0.5938.88 (Official Build) (arm64)

image

@yelodevopsi Your issue here looks to be with using unsafe-eval not inline scripts/nonce values. The nonce allows you to remove unsafe-inline but not unsafe-eval. Can you confim if the nonce is or isn't being added by viewing the "properties" panel in devtools? (See my post above)

Next appears to needs unsafe-eval when in development mode to facilitate dev tools like hot refresh I believe. If you run npm run build and npm run start you'll hopefully see that message disappear.

One trick I use is to tell next only to include unsafe-eval in the CSP when the environment is not production. Your script-src would look something like this:

`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https: http: 'unsafe-inline' ${process.env.NODE_ENV === 'production' ? '' : `'unsafe-eval'`};`

@yelodevopsi
Copy link

@sleepdotexe : Sorry, I forgot to mention that I explicitly test the ´with-strict-csp` example here on vercel/next.js.

https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp

Replacing with script-src line with yours, works like a charm with npm run dev now.
I suggest this line should be added in order to avoid more confusion.

I can confirm that only nonce is shown in the browser without the number value, bur exists in properties in dev-tools, yes:
image

image

yelodevopsi added a commit to varianter/vibes that referenced this issue Sep 25, 2023
Added custom hooks
Removed strict-dynamic, since nonce dont work as expected:
vercel/next.js#55638
@yelodevopsi
Copy link

yelodevopsi commented Sep 25, 2023

With the current canary/examples/with-strict-csp

...I can confirm that yarn build && yarn start will not append any nonce to the production build.
I'm just able to get nonce in dev-mode, hidden in the browser like @sleepdotexe has already mentioned with e.g. chrome.

PS: I was previously missing layout.tsx in my other project, causing the project to not append nonce during dev/prod.

@thomasryu
Copy link

Just pitching in, having the same issue with my project:

  • Working with yarn dev
  • Not showing with yarn build && yarn start

@cgfeel
Copy link

cgfeel commented Oct 30, 2023

Just pitching in, having the same issue with my project:

Just pitching in, having the same issue with my project:

  • Working with npm run dev
  • Not showing with npm run build && npm run start

Next.js 14.0.0
nodejs v20.9.0
npm 10.1.0
macos 13.6 (22G120)

@Laecherlich

This comment has been minimized.

@denisgursky
Copy link

Same thing,

  • Working with next dev
  • Not working with next build && next start

Also, it's not working even with next dev when Content-Security-Policy-Report-Only header is used instead of Content-Security-Policy.

@jetaggart
Copy link

I noticed that if I use the <Script> tag form next, it does a <link rel="preload" .../> in the header, which seems to not carry the nonce and break CSP. If I switch it to just a normal <script> tag it works as expected. Is the preloading in the header breaking this?

@leerob
Copy link
Member

leerob commented Nov 17, 2023

We now have docs and recommendations, including updated examples, for this: https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy#nonces

@leerob leerob closed this as completed Nov 17, 2023
Copy link
Contributor

github-actions bot commented Dec 1, 2023

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot added the locked label Dec 1, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
examples Issue/PR related to examples locked
Projects
None yet
Development

No branches or pull requests

10 participants