npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@moroz/middleware

v0.10.2

Published

Strict Content-Security-Policy (CSP) for Next.js with composable middleware

Downloads

7

Readme

Version Downloads MIT

Release

Star on GitHub Watch on GitHub Forks on GitHub

Getting started

Read the docs

Contribute

What

This package strives to make the setup and deployment of a Strict Content Security Policy (CSP) with Next.js an easy task. The design approach that makes this possible requires Next.js Middleware, which has been introduced as Beta in Next.js 12 and is stable since Next.js 12.2.

This package handles all Strict CSP conundrums for you and works for:

This package always sets CSP as HTTP response header. That enables violation reporting and report-only mode even for static pages. Plus, it provides a middleware and API handlers that make the setup of CSP violation reporting very easy.

Why

Configuring and maintaining a Content-Security-Policy (CSP) can be a tedious and error prone task. Furthermore, classic CSPs with a whitelist approach don't give you the security you might think you get from them, because in a lot of cases, they are automatically bypassable.

There is a much better option: a Hash-based/Nonce-based Strict CSP.

Such CSPs provide much better security and have always the same structure, so they don't need the maintenance that whitelist CSPs need, once they've been set up properly. But this setup is usually a a very big issue with Next.js (and with all web frameworks in general).

This is where this package comes in: To make this setup easy, convenient and a lot less error-prone.

Good Resources about (Strict) Content-Security-Policy (CSP)

  • The best overview on Strict CSPs: https://web.dev/strict-csp/

  • Great slides from a conference talk, has lots of insights and field data: https://static.sched.com/hosted_files/locomocosec2019/db/CSP%20-%20A%20Successful%20Mess%20Between%20Hardening%20and%20Mitigation%20%281%29.pdf

  • Great view on CSPs from an attacker's perspective: https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass

  • Good explanation of the strict-dynamic keyword: https://content-security-policy.com/strict-dynamic/

  • Indispensible for testing: The CSP Evaluator Extension for Google Chrome

  • Great tool to record CSP sources by browsing your site: The Laboratory Extension for Mozilla Firefox

Getting started

Install @moroz/middleware from NPM

npm -i @moroz/middleware
yarn add @moroz/middleware

Quickstart: Strict Content-Security-Policy (CSP)

Create the file middleware.js in your Next.js project folder:

// middleware.js
import {
  chainMatch,
  isPageRequest,
  csp,
  strictDynamic,
} from "@moroz/middleware";

const securityMiddleware = [
  csp({
    // your CSP base configuration with IntelliSense
    // single quotes for values like 'self' are automatic
    directives: {
      "img-src": ["self", "data:", "https://images.unsplash.com"],
      "font-src": ["self", "https://fonts.gstatic.com"],
    },
  }),
  strictDynamic(),
];

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

Create the file pages/_document.js in your Next.js project folder:

// pages/_document.js
import {
  getCspInitialProps,
  provideComponents,
} from "@moroz/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 });
    return initialProps;
  }
  render() {
    const { Head, NextScript } = provideComponents(this.props);
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

For every page under pages that uses getServerSideProps for data fetching:

import { gsspWithNonce } from "@moroz/middleware/dist/document";

// wrap data fetching with gsspWithNonce 
// to generate a nonce for CSP
export const getServerSideProps = gsspWithNonce(async (ctx) => {
  return { props: { message: "Hi, from getServerSideProps" } };
});

// the generated nonce also gets injected into page props
const Page = ({ message, nonce }) => <h1>{`${message}. Nonce ${nonce}`}</h1>;

export default Page;

Thats it. You should be all set now with a Strict CSP for your Next.js app!

Quickstart: CSP Violation Reporting

Add the reporting middleware in middleware.js:

// middleware.js
import {
  chainMatch,
  isPageRequest
  csp,
  reporting,
  strictDynamic,
  strictInlineStyles,
} from '@moroz/middleware';

const securityMiddleware = [
  csp(),
  strictDynamic(),
  reporting(),
];

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

Create the file pages/api/reporting.js to set up the reporting endpoint:

// pages/api/reporting.js
import { reporting } from "@moroz/middleware/dist/api";

/** @type {import('@moroz/middleware/dist/api').Reporter} */
const consoleLogReporter = (data) =>
  console.log(JSON.stringify(data, undefined, 2));

export default reporting(consoleLogReporter);

Thats it. Browsers will send CSP violation reports to this endpoint. You can easily react on validated reporting data by adding any number of custom reporters.

Send violation reports to Sentry

If you use Sentry for monitoring your app, there is a convenient helper sentryCspReporterForEndpoint to create a reporter, that ingests all CSP violations into your Sentry project:

// pages/api/reporting.js
import {
  reporting,
  sentryCspReporterForEndpoint,
} from "@moroz/middleware/dist/api";

// lookup at https://docs.sentry.io/product/security-policy-reporting/
const sentryCspEndpoint = process.env.SENTRY_CSP_ENDPOINT;
const sentryCspReporter = sentryCspReporterForEndpoint(sentryCspEndpoint);

export default reporting(sentryCspReporter);

Quickstart: Compose middleware

Here's an example to show how you can combine security middleware from this package with your custom middleware by using chain and chainMatch:

// middleware.js
import {
  chain,
  chainMatch,
  isPageRequest,
  csp,
  strictDynamic,
} from "@moroz/middleware";

/** @type {import('@moroz/middleware').ChainableMiddleware} */
const geoBlockMiddleware = (req) => {
  const BLOCKED_COUNTRY = "GB";
  const country = req.geo.country || "US";

  if (country === BLOCKED_COUNTRY) {
    const response = new Response("Blocked for legal reasons", { status: 451 });
    // returning response terminates the chain
    return response;
  }
};

const securityMiddleware = [csp(), strictDynamic()];

/** 
 * geoBlockMiddleware will be invoked on all requests 
 * from `BLOCKED_COUNTRY` and then block the request 
 * and terminate chain by returning a response with status 451
 * 
 * securityMiddleware will only run on requests
 * that didn't get geo-blocked and only on requests for pages
 */
export default chain(
  geoBlockMiddleware,
  chainMatch(isPageRequest)(...securityMiddleware)
);

If you only want to use the composition features from this package, there's an extra bundle @moroz/middleware/dist/compose just for that.

Contributors

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome! Check out the contributing guide for getting started!