nibtime / next-safe-middleware

Strict CSP (Content-Security-Policy) for Next.js hybrid apps https://web.dev/strict-csp/
https://next-safe-middleware.vercel.app
MIT License
78 stars 20 forks source link

No script is having nonce generated #74

Open VayneValerius opened 2 years ago

VayneValerius commented 2 years ago

TLDR

No nonces or hashes are being provided.

Problem

I have requirements for very strict-csp, so without nonces, the web-application can not be loaded. I have followed the documentation + the e2e pages and am unable to get further than the CSP directives being loaded. I am receiving no hashes or nonces. I have tried using both gsspWithNonce & gsspWithNonceAppliedToCsp. Unfortunately the nonce prop provided is always an empty string. I am also unable to get any scripts, links or styles within the provided head tag to become CSP compliant. (Well, I am CSP compliant, just nothing loads if not in report-mode!)

This is what my chunks look like in the inspector.

image

And this is the e2e examples chunks, notice the nonce tag on every script and link.

image

I am wondering if I am doing something seriously wrong when using this package. I have noticed a lot of changes in the changelog. For instance nonces are no longer auto-injected due to changes in next.js 12.2.X?

I have created a skeleton project with the package installed and a similar setup to the real codebase I am working on. It uses pnpm and has a patch for Next.js 12.2.5 to remove eval-source-map from the webpack config.

https://github.com/VayneValerius/next-safe-middleware-test

Info

_document.tsx

import {
  getCspInitialProps,
  provideComponents,
} from '@next-safe/middleware/dist/document';
import Document, { Html, Main } from 'next/document';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await getCspInitialProps({
      ctx,
      //trustifyStyles: true,
    });

    return initialProps;
  }

  render() {
    let { Head, NextScript } = provideComponents(this.props);
    return (
      <Html>
        <Head>
        // This link does NOT recieve a hash
          <link
            rel='stylesheet'
            href='//fonts.googleapis.com/css?family=Roboto:100,300,400,500,700'
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

middleware.ts

// @ts-nocheck

import {
  chainMatch,
  isPageRequest,
  csp,
  strictDynamic,
  strictInlineStyles,
  nextSafe,
} from '@next-safe/middleware';

// This is an async function that in the actual codebase changes directives based on host and path.
const cspMiddleware = csp(async ({ req }) => {
  return {
    directives: {
      'default-src': ['self', 'http://localhost:3000'],
      'base-uri': ['self'],
      'object-src': ['none'],
      'upgrade-insecure-requests': [],
      'img-src': ['self', 'data:'],
      'script-src': ['self', 'strict-dynamic'],
      'style-src': [
        'self',
        'https://fonts.googleapis.com',
        'https://use.fontawesome.com',
      ],
      'font-src': ['https://fonts.gstatic.com', 'https://use.fontawesome.com'],
      'connect-src': [
        'https://www.google-analytics.com',
        'ws:',
        'blob:',
        'http://localhost:3000',
      ],
    },
    // Don't want directives we haven't set.  Want to test csp and use csp as it would be in production
    isDev: false,
    reportOnly: true,
  };
});

const securityMiddleware = [
  nextSafe({ isDev: false, disableCsp: true }),
  cspMiddleware,
  strictDynamic(),
  strictInlineStyles(),
];

export default chainMatch(isPageRequest)(...securityMiddleware);

index.tsx

import { gsspWithNonce } from '@next-safe/middleware/dist/document';
import type { NextPage } from 'next';
import Head from 'next/head';
import styles from '../styles/Home.module.css';

export const getServerSideProps = gsspWithNonce(async (context) => {
  return { props: { message: 'Hi, from getServerSideProps' } };
});

const Home: NextPage = ({ message, nonce }) => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Middleware</title>
        <meta name='description' content='Generated by create next app' />
        <link rel='icon' href='/favicon.ico' />
      </Head>

      <main className={styles.main}>
        <h2>Index page renders</h2>
        <p>{message}</p>
        <p>{nonce}</p>
      </main>
    </div>
  );
};

export default Home;
kiily commented 1 year ago

Thanks @nibtime for all the hard work on this module. I finally had some time to get back to this whole topic for a bit but I am observing a similar behavior. No nonce is applied on the scripts either and I get reporting errors all over the place. Is this to be expected on the next 12.2.X? Adding the gsspWithNonce function or not having it at all seems to achieve the same effect and no nonce is produced for those pages

kiily commented 1 year ago

Been digging around the repo and have noticed this in the nonce.ts file, which could explain what is happening here

export const getCreateCtxNonceIdempotent = (ctx: CtxHeaders) => {
  if (process.env.NODE_ENV !== "production") {
    return "";
  }
  let _nonce = getCtxNonce(ctx);
  if (!_nonce) {
    _nonce = nonce();
    setCtxHeader(ctx, CSP_NONCE_HEADER, _nonce);
  }
  return _nonce;
};
VayneValerius commented 1 year ago

Ahh, I did wonder if it was because I was running in development mode. Should have looked through the git repo a little more. Thank you for your help with this. I removed this package and wrote a middleware myself that deals with CSP but I may replace it with this package again as its a lot cleaner.

In my opinion, I do believe that its wrong to only apply the nonces in production mode. It is often the case, especially with strict CSP, that you want/need to test it on development. Perhaps a boolean argument could be added to the middleware package instead, rather than checking for NODE_ENV?

kiily commented 1 year ago

Yeah, I eventually stumbled upon it and it worked! That being said, I think it's helpful to have it in dev although I am sure @nibtime has some thoughts on this as well. This problem is not easy to solve as a whole, especially given how much Next.js has been changing recently (see https://github.com/nibtime/next-safe-middleware/discussions/60). I think we'll take inspiration from this module and from next-safe to implement some basic security on our side. I doubt the basic Next.js headers will go away.

As for CSP, I think we'll have to compromise for a start but would be nice to have a solution without middleware as well since the API is changing a lot. NODE_ENV="production" is true if you build your app locally and then start the server on that, granted, not as nice to debug but still a good way to get it to run locally 👍

mbijon commented 1 year ago

We're having the same problem with not being able to test nonce's in pre-Prod enviroments.

@nibtime - I understand a boolean to turn off noncing could create problems if it was set wrong on Prod. To keep the strict approach but support nonce testing, would you consider booleans for common down-level env's? Like:

In my opinion, I do believe that its wrong to only apply the nonces in production mode. It is often the case, especially with strict CSP, that you want/need to test it on development. Perhaps a boolean argument could be added to the middleware package instead, rather than checking for NODE_ENV?