Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Strong CSP Support #4943

Closed
wants to merge 85 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
386245b
WIP Fix
dav-is Aug 10, 2018
1223ded
Merge branch 'canary' into canary
dav-is Aug 10, 2018
23012bc
Generate bootstrap.js
dav-is Aug 10, 2018
e5c1a51
Syntax
dav-is Aug 10, 2018
e18b1ad
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Aug 10, 2018
9531f55
Merge branch 'canary' into canary
dav-is Aug 27, 2018
b840984
Copy bootstrap.js instead of loading through webpack
dav-is Aug 28, 2018
9ec8c96
Cleanup
dav-is Aug 28, 2018
709fc5c
add a htmlescaped pathname
dav-is Aug 28, 2018
7e7092f
Fix Flow Error
dav-is Aug 28, 2018
986adfb
Remove hashes and nonces
dav-is Aug 28, 2018
9e7ee16
Minor fixes
dav-is Aug 28, 2018
f3eddab
Remove mentions of nonce/hash, add CSP to README.md
dav-is Aug 28, 2018
2c76ed9
Merge branch 'canary' into canary
dav-is Aug 28, 2018
178e4fe
Add Strict CSP Support//waiting for styled-jsx PR Merge
dav-is Aug 28, 2018
6e02af3
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Aug 28, 2018
27488ac
Merge branch 'canary' into canary
dav-is Aug 28, 2018
ea22175
Move setHeader logic to index.js, use nanoid
dav-is Aug 28, 2018
ae54cc6
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Aug 28, 2018
b1ba505
Fix eTags, if no policy dont set header, seamlessly insert nonce into…
dav-is Aug 28, 2018
b06f8ba
Fix CSP test
dav-is Aug 28, 2018
87d8b40
Whoops
dav-is Aug 28, 2018
ca2fff8
Merge branch 'canary' into canary
dav-is Aug 30, 2018
1865373
Merge branch 'canary' into canary
dav-is Aug 30, 2018
1066028
Merge branch 'canary' into canary
dav-is Aug 30, 2018
5ef8249
Add unsafeCSPMeta config option, move nonce code to this.render
dav-is Aug 30, 2018
3acf7a3
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Aug 30, 2018
de93117
Minor textual changes to README.md
dav-is Aug 30, 2018
f34704c
Merge branch 'canary' into canary
dav-is Aug 31, 2018
3aa6580
Fix HMR (WIP), check if static or not
dav-is Aug 31, 2018
d0d0288
Merge branch 'canary' into canary
dav-is Sep 4, 2018
d4ef249
Try fixing test
dav-is Sep 4, 2018
b728131
Merge branch 'canary' into canary
dav-is Sep 4, 2018
6ae263e
Run tests
dav-is Sep 4, 2018
f3cc533
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Sep 4, 2018
4b7688b
Fix tests?
dav-is Sep 4, 2018
6cf5d18
Merge branch 'canary' into canary
dav-is Sep 4, 2018
fb82831
Remove module={}
dav-is Sep 4, 2018
8f6a76f
Failing because styled-jsx PR isn't merged yet
dav-is Sep 4, 2018
a83d16c
Don't use inline styles for error pages, this change didn't work...
dav-is Sep 5, 2018
fe088b2
Merge branch 'canary' into canary
dav-is Sep 5, 2018
077d5a0
Merge branch 'canary' into canary
dav-is Sep 6, 2018
5d5643a
Fix tests, add note about error page
dav-is Sep 7, 2018
e09a1e7
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Sep 7, 2018
8343a93
Merge branch 'canary' into canary
dav-is Sep 7, 2018
96fc0cc
Minor fix, upgrade styled-jsx version - pending release
dav-is Sep 10, 2018
75227c3
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Sep 10, 2018
0c67bcd
Merge branch 'canary' into canary
dav-is Sep 24, 2018
cb76f38
Minify bootstrap.js
dav-is Sep 24, 2018
657d4e0
New dataloading technique, remove bootstrap.js
dav-is Sep 25, 2018
632d0e4
Merge branch 'canary' into canary
dav-is Sep 25, 2018
6167a49
Update package.json
dav-is Sep 26, 2018
c46ec20
Merge branch 'canary' into canary
dav-is Sep 26, 2018
1b50af9
Fixes
dav-is Sep 26, 2018
8898e59
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Sep 26, 2018
68741ef
Linter fixes
dav-is Sep 26, 2018
4f6c8dc
Use Proxy object instead of overriding prototype
dav-is Sep 26, 2018
6d5ab37
Get webpack page plugin to work
dav-is Sep 26, 2018
2c85d20
Sync with origin
dav-is Dec 4, 2018
c584de5
Reapply changes to updated branch
dav-is Dec 5, 2018
db4b51c
Merge branch 'canary' into canary
dav-is Dec 5, 2018
4ec0e01
Use classname for error-overlay
dav-is Dec 5, 2018
8a6de18
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Dec 5, 2018
96c2040
Define styles in js instead of jsx
dav-is Dec 5, 2018
cd718cc
Cleanup variables
dav-is Dec 5, 2018
56bd0e5
Remove AutoDll plugin to avoid unsafe-eval, fix typo in _doc
dav-is Dec 5, 2018
0bbdd3d
Fix tests
dav-is Dec 5, 2018
d8cd11c
Fix #5674
dav-is Dec 5, 2018
ba45a21
Fix tests because NEXT_DATA doesn't need crossOrigin and nonce attrib…
dav-is Dec 5, 2018
5d2c75b
Azure failed, is it because of a trailing slash?
dav-is Dec 6, 2018
d1017f8
Merge branch 'canary' into canary
dav-is Dec 6, 2018
fd2300a
Debug why Azure is failing tests
dav-is Dec 6, 2018
dfe452c
Merge branch 'canary' into canary
dav-is Dec 6, 2018
3c42323
No more default CSP, README is more clear, all integration tests use CSP
dav-is Dec 11, 2018
5697389
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Dec 11, 2018
9f9d2c4
Sync with upstream
dav-is Dec 11, 2018
98509ca
Merge branch 'canary' into canary
dav-is Dec 11, 2018
15cf4f4
Fix ETags and remove unused dep
dav-is Dec 11, 2018
bbd7306
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Dec 11, 2018
eaa0672
Merge branch 'canary' into canary
dav-is Dec 12, 2018
8fea414
Readme edits
dav-is Dec 12, 2018
3608c1a
Merge branch 'canary' of github.com:dav-is/next.js into canary
dav-is Dec 13, 2018
abfaa78
Remove crossOrigin PR
dav-is Dec 13, 2018
247aa1d
Merge remote-tracking branch 'upstream/canary' into canary
dav-is Dec 13, 2018
cf30cb6
Remove inline styles from _error.js and error-debug.js
dav-is Dec 13, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/with-dynamic-csp/README.md
Original file line number Diff line number Diff line change
@@ -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-dynamic-csp)

# Strict CSP example

## 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-dynamic-csp with-dynamic-csp-app
# or
yarn create next-app --example with-dynamic-csp with-dynamic-csp-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-dynamic-csp
cd with-dynamic-csp
```

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

**If** you are having difficulty setting up a *standard* Content Security Policy, then you might need to use a [dynamic policy](https://csp.withgoogle.com/docs/strict-csp.html). This is less secure than effective whitelisting, but if your project cannot employ effective whitelisting, this can be a secure alternative. For it to work, we need to generate a nonce on every request, so this is limited to server rendered projects.

Next.js supports generating nonces for our CSP out of the box.

Note that when using `style-src 'nonce-{style-nonce}' 'unsafe-inline';` the nonce is automatically applied to your `styled-jsx` styles when using `<style jsx>`. This is the most secure way of using `styled-jsx`. Next.js adds a meta tag to support [Material UI's CSS in JSS](https://material-ui.com/css-in-js/advanced/#content-security-policy-csp) or when using [JSS](https://github.com/cssinjs/jss/blob/master/docs/csp.md) as well.
3 changes: 3 additions & 0 deletions examples/with-dynamic-csp/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
contentSecurityPolicy: "base-uri 'none';object-src 'none';script-src: 'nonce-{script-nonce}' 'strict-dynamic' 'unsafe-inline' http: https:;style-src 'nonce-{style-nonce}' 'unsafe-inline';"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "with-strict-csp-hash",
"version": "1.0.0",
"name": "with-dynamic-csp",
"version": "2.0.0",
"scripts": {
"dev": "next",
"build": "next build",
Expand All @@ -10,6 +10,5 @@
"next": "latest",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"license": "ISC"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ const inlineScript = (body, nonce) => (
export default class MyDocument extends Document {
static async getInitialProps (ctx) {
const initialProps = await Document.getInitialProps(ctx)
const { nonce } = ctx.res.locals
return { ...initialProps, nonce }
const { scriptNonce, styleNonce } = ctx
return { ...initialProps, scriptNonce, styleNonce }
}

render () {
const { nonce } = this.props
const { scriptNonce, styleNonce } = this.props
return (
<html>
<Head nonce={nonce}>
{inlineScript(`console.log('Inline script with nonce')`, nonce)}
<Head>
<style nonce={styleNonce}>{`
body {
background: black;
color: white;
}
`}</style>
{inlineScript(`console.log('Inline script with nonce')`, scriptNonce)}
</Head>
<body>
<Main />
<NextScript nonce={nonce} />
<NextScript />
</body>
</html>
)
Expand Down
8 changes: 8 additions & 0 deletions examples/with-dynamic-csp/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default () => <div>
<h1>Hello World</h1>
<style jsx>{`
h1 {
font-size: 150px;
}
`}</style>
</div>
48 changes: 0 additions & 48 deletions examples/with-strict-csp-hash/README.md

This file was deleted.

26 changes: 0 additions & 26 deletions examples/with-strict-csp-hash/pages/_document.js

This file was deleted.

3 changes: 0 additions & 3 deletions examples/with-strict-csp-hash/pages/index.js

This file was deleted.

46 changes: 0 additions & 46 deletions examples/with-strict-csp/README.md

This file was deleted.

31 changes: 0 additions & 31 deletions examples/with-strict-csp/csp.js

This file was deleted.

17 changes: 0 additions & 17 deletions examples/with-strict-csp/package.json

This file was deleted.

3 changes: 0 additions & 3 deletions examples/with-strict-csp/pages/index.js

This file was deleted.

22 changes: 0 additions & 22 deletions examples/with-strict-csp/server.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/next-server/server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const defaultConfig = {
useFileSystemPublicRoutes: true,
generateBuildId: () => null,
generateEtags: true,
contentSecurityPolicy: null,
pageExtensions: ['jsx', 'js']
}

Expand Down
20 changes: 20 additions & 0 deletions packages/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolve, join, sep } from 'path'
import { parse as parseUrl, UrlWithParsedQuery } from 'url'
import { parse as parseQs, ParsedUrlQuery } from 'querystring'
import fs from 'fs'
import nanoid from 'nanoid'
import {renderToHTML} from './render'
import {sendHTML} from './send-html'
import {serveStatic} from './serve-static'
Expand Down Expand Up @@ -31,6 +32,9 @@ export default class Server {
buildId: string
renderOpts: {
staticMarkup: boolean,
csp?: string,
scriptNonce?: string,
styleNonce?: string,
distDir: string,
buildId: string,
generateEtags: boolean,
Expand Down Expand Up @@ -203,6 +207,18 @@ export default class Server {
}

public async render (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery = {}, parsedUrl?: UrlWithParsedQuery): Promise<void> {
if (this.nextConfig.contentSecurityPolicy) {
if (!this.renderOpts.staticMarkup) {
this.renderOpts.csp = this.nextConfig.contentSecurityPolicy
.replace(/{script-nonce}/gi, () => (this.renderOpts.scriptNonce = this.genNonce()))
.replace(/{style-nonce}/gi, () => (this.renderOpts.styleNonce = this.genNonce()))

this.renderOpts.csp && res.setHeader('Content-Security-Policy', this.renderOpts.csp)
} else {
this.renderOpts.csp = this.nextConfig.contentSecurityPolicy
}
}

const url: any = req.url
if (isInternalUrl(url)) {
return this.handleRequest(req, res, parsedUrl)
Expand Down Expand Up @@ -304,4 +320,8 @@ export default class Server {
throw err
}
}

genNonce () {
return Buffer.from(nanoid(32)).toString('base64')
}
}
Loading