diff --git a/examples/with-strict-csp-hash/README.md b/examples/with-strict-csp-hash/README.md new file mode 100644 index 00000000..21b5e667 --- /dev/null +++ b/examples/with-strict-csp-hash/README.md @@ -0,0 +1,48 @@ +[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-strict-csp-hash) + +# Example app with strict CSP generating script hash + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: + +```bash +npx create-next-app --example with-strict-csp-hash with-strict-csp-hash-app +# or +yarn create next-app --example with-strict-csp-hash with-strict-csp-hash-app +``` + +### Download manually + +Download the example: + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-strict-csp-hash +cd with-strict-csp-hash +``` + +Install it and run: + +```bash +npm install +npm run dev +# or +yarn +yarn dev +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example + +This example features how you can set up a strict CSP for your pages whitelisting next's inline bootstrap script by hash. +In contrast to the example `with-strict-csp` based on nonces, this way doesn't require running a server to generate fresh nonce values on every document request. +It defines the CSP by document `meta` tag. + +Note: There are still valid cases for using a nonce in case you need to inline scripts or styles for which calculating a hash is not feasible. diff --git a/examples/with-strict-csp-hash/package.json b/examples/with-strict-csp-hash/package.json new file mode 100644 index 00000000..bcb456b4 --- /dev/null +++ b/examples/with-strict-csp-hash/package.json @@ -0,0 +1,15 @@ +{ + "name": "with-strict-csp-hash", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^16.0.0", + "react-dom": "^16.0.0" + }, + "license": "ISC" +} diff --git a/examples/with-strict-csp-hash/pages/_document.js b/examples/with-strict-csp-hash/pages/_document.js new file mode 100644 index 00000000..a894ff12 --- /dev/null +++ b/examples/with-strict-csp-hash/pages/_document.js @@ -0,0 +1,26 @@ +import crypto from 'crypto' +import Document, { Head, Main, NextScript } from 'next/document' + +const cspHashOf = (text) => { + const hash = crypto.createHash('sha256') + hash.update(text) + return `'sha256-${hash.digest('base64')}'` +} + +export default class extends Document { + render () { + const csp = `default-src 'self'; script-src 'self' ${cspHashOf(NextScript.getInlineScriptSource(this.props))}` + + return ( + + + + + +
+ + + + ) + } +} diff --git a/examples/with-strict-csp-hash/pages/index.js b/examples/with-strict-csp-hash/pages/index.js new file mode 100644 index 00000000..3d446a4e --- /dev/null +++ b/examples/with-strict-csp-hash/pages/index.js @@ -0,0 +1,3 @@ +export default () => ( +
Hello World
+) diff --git a/server/document.js b/server/document.js index 49dc22f0..361bb437 100644 --- a/server/document.js +++ b/server/document.js @@ -176,6 +176,28 @@ export class NextScript extends Component { }) } + static getInlineScriptSource (documentProps) { + const { __NEXT_DATA__ } = documentProps + const { page, pathname } = __NEXT_DATA__ + + return ` + __NEXT_DATA__ = ${htmlescape(__NEXT_DATA__)} + module={} + __NEXT_LOADED_PAGES__ = [] + + __NEXT_REGISTER_PAGE = function (route, fn) { + __NEXT_LOADED_PAGES__.push({ route: route, fn: fn }) + }${page === '_error' ? ` + + __NEXT_REGISTER_PAGE(${htmlescape(pathname)}, function() { + var error = new Error('Page does not exist: ${htmlescape(pathname)}') + error.statusCode = 404 + + return { error: error } + })`: ''} + ` + } + render () { const { staticMarkup, assetPrefix, __NEXT_DATA__ } = this.context._documentProps const { page, pathname, buildId } = __NEXT_DATA__ @@ -183,22 +205,7 @@ export class NextScript extends Component { return {staticMarkup ? null :