tbd_
tbd_

Reputation: 1268

How to debug "Content-Security-Policy (CSP)" and nonces in NextJS

I am trying to setup the CSP header in my NextJS app (App router).

I added the "Content Security Policy" Security Header, specifically the "src-script" directive. BUT I am getting an error, basically saying that there is some unknown script host (besides the ones listed) that is violating this directive. Chrome isn't helpful in that it doesn't know WHICH script is violating this.

Problem

How do I identify all the src-script sources in my app, without having to pore through the codebase?

vercel.json snippet:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' *.posthog.com; style-src 'self';"
        }
      ]
    }
  ]
}

Error

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src" 'self' https://.posthog.com".*

Upvotes: 2

Views: 2350

Answers (2)

sledgeweight
sledgeweight

Reputation: 8105

In addition to the above, if anyone is finding a conflicting warning after constructing CSPs with unsafe-eval on script-src triggered by a next/script due it using dangerouslySetInnerHTML, set the prop strategy="lazyOnload".

<Script
  dangerouslySetInnerHTML={{ ...etc
  strategy="lazyOnload"

Also, took me a while to figure i needed my middleware in src/middleware.ts, as i have my project with Payload and the FE dir structure looks like: src/app/(frontend)/

Upvotes: 0

tbd_
tbd_

Reputation: 1268

OK I figured it out. There were several issues. SUPER annoying that nextjs docs don't make these explicit.

My original attempt was to add CSP as part of vercel.json, but you should set it in middleware instead.

Annoying nextjs landmine #1. Nonces get automagically added to your next scripts in prod, once you set them up (they don't get filled in during dev mode).

After following this doc and adding the CSP middleware, it wasn't clear how the damn nonce's were getting added to the _next script tags. Answer: They just automagically get added!! (in dev it just says <script nonce> - very hard to tell if the nonce value is getting applied).

Annoying nextjs landmine #2. You have to make your root layout.tsx to dynamic.

Thanks to this answer I was able to answer this one. Again, the NextJS docs on this are confusing. Yes, I know, it does explicitly say:

Every time a page is viewed, a fresh nonce should be generated. This means that you must use dynamic rendering to add nonces.

But as someone who's not so familiar with Nextjs, it would've been nice if the example spelled it out. Something like "to use dynamic rendering, add this to your root layout.tsx file: export const dynamic = "force-dynamic""

3. Your CSP contents need to be dynamic based on your dev/prod environment.

If you're in dev, you need it to be unsafe-eval otherwise it won't work locally. Here's my CSP string:

  const cspHeader = `
    default-src 'self';
    connect-src 'self' some.domain.com another.domain.com *.example.com ${isDev ? `${process.env.NEXT_PUBLIC_API_URL}` : ''};
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
    style-src 'self' 'unsafe-inline';
    img-src 'self' blob: data: some.domain.com;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`

Where NEXT_PUBLIC_API_URL is localhost:8000 or whatever your backend URL is.

4. Whenever you integrate a new domain into your app, you need to include them in the connect-src directive, not script-src. This took a lot of experimenting, but I got there. #3 above for examples.

For example, if you're loading posthog into your app, it should be:

connect-src 'self' *.posthog.com;

Other helpful links: here

Upvotes: 10

Related Questions