Strict Content-Security-Policy (CSP) for Next.js with composable middleware
npm install @next-safe/middleware
@next-safe/middlewareStrict Content-Security-Policy (CSP) for Next.js with composable middleware
Works for hybrid apps and supports pages with any data fetching method.
Always sets CSP by HTTP Response header and enables easy setup of reporting.
[![Version][version-badge]][package]
[![Downloads][downloads-badge]][npmtrends]
[![MIT][license-badge]][license]
[![Release][release-status-badge]][release-status]
[![Star on GitHub][github-star-badge]][github-star]
[![Watch on GitHub][github-watch-badge]][github-watch]
[![Forks on GitHub][github-forks-badge]][github-forks]
[package]: https://npmjs.com/package/@next-safe/middleware
[npmtrends]: https://www.npmtrends.com/@next-safe/middleware
[version-badge]: https://img.shields.io/npm/v/@next-safe/middleware.svg?style=flat-square
[downloads-badge]: https://img.shields.io/npm/dm/next-safe.svg?style=flat-square
[license]: LICENSE
[license-badge]: https://img.shields.io/github/license/nibtime/next-safe-middleware?style=flat-square
[release-status]: https://github.com/nibtime/next-safe-middleware/actions/workflows/release.yml
[release-status-badge]: https://img.shields.io/github/workflow/status/nibtime/next-safe-middleware/Release?style=flat-square&label=release
[github-watch]: https://github.com/nibtime/next-safe-middleware/watchers
[github-watch-badge]: https://img.shields.io/github/watchers/nibtime/next-safe-middleware.svg?style=social
[github-star]: https://github.com/nibtime/next-safe-middleware/stargazers
[github-star-badge]: https://img.shields.io/github/stars/nibtime/next-safe-middleware.svg?style=social
[github-forks]: https://github.com/nibtime/next-safe-middleware/network/members
[github-forks-badge]: https://img.shields.io/github/forks/nibtime/next-safe-middleware.svg?style=social
This package handles all Strict CSP conundrums for you and works for:
* pages with getStaticProps - Hash-based
* pages with getServerSideProps - Nonce-based
* pages with getStaticProps + revalidate (ISR) - Hash-based
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.
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.
* 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
Install @next-safe/middleware from NPM
``bash`
npm -i @next-safe/middleware
`bash`
yarn add @next-safe/middleware
Create the file middleware.js in your Next.js project folder:
`js
// middleware.js
import {
chainMatch,
isPageRequest,
csp,
strictDynamic,
} from "@next-safe/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:
`jsx
// pages/_document.js
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 });
return initialProps;
}
render() {
const { Head, NextScript } = provideComponents(this.props);
return (
For every page under
pages that uses getServerSideProps for data fetching:`js
import { gsspWithNonce } from "@next-safe/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 }) =>
{
${message}. Nonce ${nonce}};export default Page;
`Thats it. You should be all set now with a Strict CSP for your Next.js app!
$3
Add the
reporting middleware in middleware.js:`js
// middleware.js
import {
chainMatch,
isPageRequest
csp,
reporting,
strictDynamic,
strictInlineStyles,
} from '@next-safe/middleware';const securityMiddleware = [
csp(),
strictDynamic(),
reporting(),
];
export default chainMatch(isPageRequest)(...securityMiddleware);
`Create the file
pages/api/reporting.js to set up the reporting endpoint:`js
// pages/api/reporting.js
import { reporting } from "@next-safe/middleware/dist/api";/* @type {import('@next-safe/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:`jsx
// pages/api/reporting.js
import {
reporting,
sentryCspReporterForEndpoint,
} from "@next-safe/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);
`$3
Here's an example to show how you can combine security middleware from this package with your custom middleware by using
chain and chainMatch:`js
// middleware.js
import {
chain,
chainMatch,
isPageRequest,
csp,
strictDynamic,
} from "@next-safe/middleware";/* @type {import('@next-safe/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
@next-safe/middleware/dist/compose` just for that.Thanks goes to these wonderful people (emoji key):
nibtime ๐ป ๐ ๐ ๐ โ ๏ธ ๐ค ๐ก | Alex Turpin ๐ ๐ ๐ค | Benoรฎt Faucon ๐ | Renรฉ Schubert ๐ | Ben Hodgson ๐ | Stephan Bรถnnemann-Walenta ๐ป | Shamil Yakupov ๐ |
DuCanhGH ๐ |
This project follows the all-contributors specification. Contributions of any kind welcome! Check out the contributing guide for getting started!