-
Notifications
You must be signed in to change notification settings - Fork 27k
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
[WIP] Strong CSP Support #4943
Conversation
server/document.js
Outdated
}} />} | ||
{staticMarkup ? null : <script | ||
id="server-app-state" | ||
type="application/json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you use another tag type instead for transferring the app-state? I suppose even though its type is application/json
some browsers may treat it as an inline script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. This is an html standard. <script type=“application/json”>
isn’t JavaScript and CSP defines script-src
as “Specifies valid sources for JavaScript”. This is also the most valid way for SSR to use CSP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't be so optimistic about all browsers implementing this correctly. Anyway, alternatively I could think about using a data-
attribute on the head
element to hold the app-state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.drupal.org/node/2513818
If it means anything to you, a quick google search found that Drupal is using this method.
server/document.js
Outdated
), | ||
}} | ||
/>} | ||
<script |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This defeats the purpose of this PR, defines an inline script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As stated above, CSP ignores non-JavaScript <script> tags. Using a <script> tag is the html standard of transmitting JSON as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't see any type
attribute set on this script
tag, so it's treated as javascript. Have you been testing your PR with a strict CSP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I just realized which script tag this comment was attached to. This script tag is temporary until I can figure out how to define this through webpack (this is why it’s a WIP)
server/document.js
Outdated
}} | ||
/> | ||
<script | ||
src={`${assetPrefix}/_next/static/runtime/bootstrap.js`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see this discussed upfront, since this adds a network request. I'd rather prefer to have the app-state/bootstrap code integrated into some common bundle (_app
?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like all the pages are loaded asyncronously so they could be loaded before runtime/main.js which is where next is loaded. I added a new file called runtime/bootstrap.js that should be small and won’t be loaded asyncronously and will always run before everything else. This is a potential blocking process, but if we keep the file small and never change it so we can keep a huge cache time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app-state is data (nothing that is executed) that will change with each request to SSR, but the bootstrap.js can be cached instead of being sent with each html request so it’s actually going to save bytes over time, and if you are using http/2 you can have more requests without any performance degradation.
This is ready for review! 😄 CC: @dbo |
README.md
Outdated
|
||
#### Content Security Policy | ||
|
||
Next.js *mostly* supports [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). You can use the following CSP policy to get started: `default-src 'self'; style-src 'self' 'unsafe-inline';` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think unsafe-inline
is a bad suggestion for CSP and not needed, since you could define a hash for the inline script (NextScript
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inline style. Completely different issue. Trust me. <style> blocks are just randomly added and removed from the DOM. It takes some special logic to get that working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m going to see if I can get automatic CSP support in Nexjs today including nonces for generates styles.
<Head nonce='test-nonce'> | ||
{csp ? <meta httpEquiv='Content-Security-Policy' content={csp} /> : null} | ||
<Head> | ||
{this.props.withCSP ? <meta httpEquiv='Content-Security-Policy' content="default-src 'self'; style-src 'self' 'unsafe-inline';" /> : null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually weakening the current CSP of the test. Why are you suggesting this? It does not make sense to me, users should rather define a hash in their CSP for the exceptional inline scripts they need, or nonces (if they run a server to reliably generate fresh nones, of course).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not true at all. Look closely at your CSP policy when you were using hashes: https://github.com/zeit/next.js/blob/canary/test/integration/app-document/pages/_document.js#L38
Inline styles is a different issue and a much more minor issue as it’s only a tiny visual security hole (instead of something being executed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I misread that rule.
client/bootstrap.js
Outdated
@@ -0,0 +1,17 @@ | |||
window.module = {} | |||
window.__NEXT_DATA__ = JSON.parse( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done in client/index.js
right 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. There’s a function appended to the beginning of each page.js and they load async so they could be called at any moment. This means that these functions need to be defined before any other scripts are loaded, which is only possible by loading it first as the client/index.js could be loaded third and two pages have loaded first and there’s no function to register them.
Also, module={}
has to be defined before any webpack files load as they begin with module.exports
and will say module is undefined. I’m guessing these are all the hoops you had to jump through to get asynchronous loading working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
window.__NEXT_DATA__ = JSON.parse(
I meant reading the __NEXT_DATA__
, the rest still has to be defined beforehand like you said.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timneutkens Becuase __NEXT_DATA__
is used in the lines following: https://github.com/zeit/next.js/pull/4943/files#diff-358a769020e11c021478d8f344f90a7eR11
client/bootstrap.js
Outdated
@@ -0,0 +1,17 @@ | |||
window.module = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is still needed with the latest webpack, it might not be needed anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing it breaks everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed here: #5093
server/document.js
Outdated
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} as='script' nonce={this.props.nonce} />} | ||
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} as='script' nonce={this.props.nonce} /> | ||
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_error.js`} as='script' nonce={this.props.nonce} /> | ||
<link rel='preload' href={`${assetPrefix}/_next/static/runtime/bootstrap.js`} as='script' /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an agreement about a further script request, or can the bootstrap code be integrated into a bundle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no guarantee about execution order of scripts, so it would be hard to ensure that the functions in bootstrap.js
are run first. I looked into adding it into the webpack runtime, but it seemed more complicated than it was worth.
server/document.js
Outdated
render () { | ||
const { staticMarkup, assetPrefix, devFiles, __NEXT_DATA__ } = this.context._documentProps | ||
const { page, pathname, buildId } = __NEXT_DATA__ | ||
const pagePathname = getPagePathname(pathname) | ||
|
||
__NEXT_DATA__.cleanPathname = htmlescape(__NEXT_DATA__.pathname); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the possible attack vector here, assuming that's the reason you're escaping the pathname 🤔 pathname
is already part of __NEXT_DATA__
, and __NEXT_DATA__
gets htmlescaped too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn’t want to have to bundle htmlescape into the the bootstrap file. I just escape the value early: https://github.com/zeit/next.js/blob/canary/server/document.js#L192
Turns out that CSP doesn't like <iframe>
<script>alert('test')</script>
</iframe> so Also, we still have a few tests and error pages that use inline styles. |
We're more than happy to implement any changes needed to make the overlay CSP compatible. I'm not a big fan of facebook/create-react-app#5943 because it looks like the user mounting the overlay will have to be aware of what styles to apply. How do libraries like We've been slow to adopt CSP because the overlay is mainly a development tool, not a production addon. I'm open to any proposals. Can we still do things like this? |
@Timer The root iframe has styles applied to it inline. The PR allows us to move the styles into The problem with using an iframe is that you can't write everything inline. You'd need to create a new page to view in an iframe and use some kind of API to communicate between the iframe and the runtime error. Google does something kind of similar with Auth although I don't know how they do it... |
Are pages allowed to inject their own new style tags? Can we create a new style tag in the current page and then assign it to the iframe using |
In order to have safe CSP styles all styles need to either be in a stylesheet externally (which could be done inside of The unsafe-inline of scripts is a bit more serious and difficult to solve. I'm diving deeper into how iframes work now |
@Timer I wrote a small html page to demonstrate what I mean. It's sort of a complicated setup division that makes me question if we should even be using an iframe for this... Code: https://github.com/dav-is/csp-iframe-example Also, you meantioned if you can still do this and you can by using The benefit of having a strict CSP during development is so you can catch issues before testing the production code. You could build up a concept that doesn't work with your policy (that could be determined and enforced as a team) and you realize it doesn't work with the policy after you finished and ran tests. Or maybe someone doesn't have reliable production build tests and a CSP bug slips through because it worked in development mode. |
There is some update from this PR? there would be awesome to be in the next release version. |
This is not going to land before v8. Will be worked on at a later time. |
I’m happy to see there’s progress on this issue 🙂 Before I dive in and try it myself, is there a way for me to manually add CSP to my application or is there still some issues in v8 that prevent that? In the v8 release blog post they mention that before v8 it was only possible using |
@baldurh CSP works in production mode, but you'll run into some errors in development, so you'll need to test your CSP on your production build for now. There's also support for |
Going to close this PR as it's outdated. We'll tackle it later on 👌 @dav-is has since joined the Next.js team 👍 |
@timneutkens Hi! Can you link the follow-up PR for those of us who are following along from past issues here? I'm wondering what the status of CSP in Next is, but this seems like the end of the trail for people who came from the previous issue right now 🙀 |
any new? documentation? |
Here's the example: https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp. You can search the releases for the related PRs that removed inline JS. |
@conioX Looks like you can include Content Security Policy headers in NextJS 9.5 https://nextjs.org/blog/next-9-5#headers |
The last item in the above TODO list reads
and the provided example only mentions
Is this something that we can expect in the near future? I would appreciate it very much as I'm struggling to make CSP to work with Material-UI. |
Support for Content Security Policy requires testing Next.js with the strictest policy possible. This is to support the use of a strict policy on hardened Next.js application. To be compatible, Next.js must not use any inline styles or scripts. No dependencies can use
eval()
. This is all to help prevent XSS attacks.With recent reports of malicious packages on npm, users can benefit from whitelisting the origins they communicate with avoid leaking private data (unintentionally or maliciously).
TODO
.js
(657d4e0) => (a2553bb)<script type="application/json">
([WIP] Strong CSP Support #4943 (review)) => (Move __NEXT_DATA__ into an application/json script tag #5584)<style>
tags withstyled-jsx
(Support Strict CSP with a nonce styled-jsx#482)next-server
contentSecurityPolicy
dynamic-csp
example to use native generatingcrossOrigin
config option and set inNEXT_DATA
(Fix #5674 Append crossOrigin on the client side too, add config option for crossOrigin #5873)_error.js
andserver/error-debug.js
react-error-overlay
([WIP] Strong CSP Support #4943 (comment)) ( [react-error-overlay] Add an Optional Runtime Option Style iFrame with a Class facebook/create-react-app#5943)New Configuration Options
Server Side Rendering State is stored inside a
<script type="application/json">
which is not executed so is not considered an inline script.If using static exporting, we can't set a CSP in a header, so the policy will be added in a meta tag. If using static exporting nonces cannot work, so either
'unsafe-inline'
is required for styles or you will have to use a different CSS-in-JS library that can export statically as well. It would be cool if we could output anow.json
file to add a CSP header to static hosting.