remix: Security - Can't use CSP that blocks 'unsafe-inline';
To improve security and mitigate cross site scripting threats, sites should set a Content Security Policy (CSP). I would like to restrict to script-src: self to block inline scripts, but Remix crashes on hydration when this is set.
Remix version: 0.17.2
Workaround: don’t hydrate, server rendered pages still work 👌 🚀
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 12
- Comments: 32 (15 by maintainers)
Any word on this? I’d like to set a content security policy for a new project and this is preventing us from doing so.
I think we can get rid of this inline script.
__remixContextinto a json script tag__remixRouteModulesTo fix "Warning: Prop nonce did not match. Server: “” Client: “irn2qSvqJ-0xG9HuIos8DrJjQBtqYMk8L3PVz4qyG74muQ” ", you need to add:
Full code:
I was going to suggest changing to use
script[type="application/json"]If that worked with thescript-src: 'self'CSP, good to know that works, maybe Remix should switch to that, also since there’s only one script for the content it could use something like:And then query by the ID.
How come
<Links/>does not support nonce?Hmm, seems to be working for me with v1.19.3
@ngbrown Sorry for being unclear. I was referring to your earlier comment saying
I thought that what you meant by that was that there is an error in the browser console, namely:
Nonce doesn’t work if you want caching as it needs to be generated for every single request, otherwise it’s trivial to bypass. I have a section on “Nonce-based CSP” in the post I linked.
@cskeppstedt I’m pretty sure the fifth argument wasn‘t added until pretty recently, and only in 2.x.
Edit: Turning this into a mini-tutorial as I figured out my derp: I mounted the
<NonceProvider>withnonce={nonce}instead ofvalue={nonce}.In addition, the way Remix adds dynamic scripts requires
'script-src-elem'to be defined in addition to'script-src'.@samcolby @ahuth How are you piecing this together? Adding the nonce itself doesn’t seem to be enough. Is there a guide somewhere?
I’ve added …
server.mjsproviders/nonce.jsentry.server.js(both bot and browser handlers)root.js… and still CSP is whining in my console.
Did I forget anything obvious?
Aha! Thank you @ahuth that’s much easier to follow!
Edit: See ahuth’s solution below, which is much better than mine 🤦 .
For anyone as confused by this thread as I was… At the time of writing this isn’t currently working on Kent’s site .
And the NonceContext approach doesn’t add the nonce to the html, which I assume is why it is not working on Kent’s site.Using the other comments in this thread, you can get it work using remix
1.19.3with all V2 flags enabled usingIn
root.tsxThen in
handleRequestorhandleBrowserRequestetc…Love to know if there is a better way of doing this without using the
loaderthough.Or ideally, all inline scripts can be removed so we don’t have to worry about this at all 🤞 .
Was also looking at this today. Not sure if this helps - but Kent Dodd’s site @kentcdodds - is also using nonce values, generated in his express server, and available via loader context… https://github.com/kentcdodds/kentcdodds.com/blob/main/app/root.tsx
@EvgeniyBudaev thank you! This works great!
For anyone unclear on why this works - the nonce attribute is stripped from the script elements as soon as the page loads. Then when client hydration happens, the nonce values are re-applied, and are then updated again every time the loader is re-fetched. This shouldn’t be happening anyway, and it would be great if there was added documentation in the Remix docs to ensure the nonces are only provided on the initial server-side load.
ref: https://html.spec.whatwg.org/multipage/urls-and-fetching.html#nonce-attributes : “Elements that have a nonce content attribute ensure that the cryptographic nonce is only exposed to script (and not to side-channels like CSS attribute selectors) by taking the value from the content attribute, moving it into an internal slot named [[CryptographicNonce]], exposing it to script via the HTMLOrSVGElement interface mixin, and setting the content attribute to the empty string. Unless otherwise specified, the slot’s value is the empty string.”
Hi @davidpfahler, I’m sure you’re discovering that CSP is a fairly complex feature. With as little information you’ve given it’s hard to know what is going on. Maybe open a discussion for more interactive follow up?
I’ll add that the Scroll Restoration code also adds some additional script inline that would need to mitigated as well: https://github.com/remix-run/remix/blob/main/packages/remix-react/scroll-restoration.tsx
Inspecting the source I see there’s the
__remixContextwhich looks like a good candidate for an inline json block. I wrote about an approach for Inline Data With a Content Security Policy a while ago that might be of interest. Looking back, I think I was a bit verbose writing that post, but hopefully the content can help.