-
Notifications
You must be signed in to change notification settings - Fork 286
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
Add a default content security policy to the skeleton template #1235
Conversation
@davidhousedev @duncan-fairley I'm curious if you all use a CSP in your sites? If so, do you use nonces and how have you set them up with Remix? |
I don't think there will be hydration issue if we set client with I feel we can abstract some Hydrogen components:
While the codes in
Since graphqli route is a virtual route, can we just define a separate CSP header so that it is valid? It's only gonna exist in dev mode anyways.
Feels like lazy on our part if we allow it. If we got lazy, people who clone with our template will also be lazy. (edit) This is bad. Questions: How would nounce work for dynamic scripts on sub navigation? For example, I have a instagram feed that only show up on about page. I don't want this script to load on home page and only loads it when user navigates to about page. Would the loader requests contain the nounce? does this validates the CSP? Should each loader request creates a new nounce? but that won't match the main request nounce since it is a single page app. Reading a couple resources, I think we definitely should have a recommended setting with clear instructions on what it allows. CSP in SPA seems like a bit more work is involved. |
@blittle unfortunately this isn't something we've had a chance to look into yet. I'm definitely curious to see what you all recommend! |
One more thing we need to look into is that if there are any missing supports that we need from Remix. We would need to add support for nounce from |
@wizardlyhel I think this actually can be much simpler, I think all of the logic in |
@wizardlyhel I updated the PR to package the following functionality into Hydrogen:
I think this works, but I wish it all could somehow be wrapped up better in a way where we don't need to expose so many individual exports. But it's tricky to do while both React components and non-react functionality all access to the nonce. |
@juanpprieto at the moment the default CSP allows inline styles, this is because the skeleton route uses inline styles for active elements. I assume this is necessary for now. |
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.
Nice~ This is much cleaner
packages/hydrogen/src/csp/csp.ts
Outdated
function toHexString(byteArray: Uint8Array) { | ||
return Array.from(byteArray, function (byte) { | ||
return ('0' + (byte & 0xff).toString(16)).slice(-2); | ||
}).join(''); | ||
} |
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 we reuse buildUUID
function from hydrogen-react
?
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 isn't a UUID though, at least all nonce's I've seen are not.
export const Script = forwardRef<HTMLScriptElement, ScriptProps>( | ||
(props, ref) => { | ||
const nonce = useNonce(); | ||
return <script {...props} nonce={nonce} ref={ref} />; | ||
}, | ||
); |
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.
Do we need to make this export a different name than Script
to avoid import confusion?
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 kind of import confusion? I assume if we later have other things we do for 3p scripts, we wouldn't want multiple Script
components.
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.
Along with that, perhaps we make the CSPProvider
a more generic HydrogenProvider
, so down the road we can add more logic to a single provider, instead of exposing many?
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.
If we add, for example, integration with partytown or something, would we add it to this same component?
Also, +1 to have a generic provider for Hydrogen instead of CSP-specific.
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.
Thanks for pushing this Bret!
A couple of things:
- We should also add the nonce to the scripts in the ErrorBoundary (including the ScrollRestoration like I did in the suggestion comment below).
- MiniOxygen is adding a script for live reload and that one doesn't include the nonce, so refreshing on changes breaks. I'll try to fix this and release a new version of MiniOxygen. ==> https://github.com/Shopify/mini-oxygen/pull/522
export const Script = forwardRef<HTMLScriptElement, ScriptProps>( | ||
(props, ref) => { | ||
const nonce = useNonce(); | ||
return <script {...props} nonce={nonce} ref={ref} />; | ||
}, | ||
); |
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.
If we add, for example, integration with partytown or something, would we add it to this same component?
Also, +1 to have a generic provider for Hydrogen instead of CSP-specific.
@frandiox I really like the simplicity of having all the logic only within |
GraphiQL is also only shown in development so we don't need to worry about that one. That leaves us with API responses... I'm not an expert on this topic but I think API responses wouldn't benefit from most of the uses of a CSP. There might be some minor benefits but perhaps something we can revisit when it's simpler to set it up in Remix? Right now we'd need to also modify |
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 like how this has evolved 👍
styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.shopify.com'], | ||
connectSrc: ['self', 'https://monorail-edge.shopifysvc.com'], |
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.
These might be overwritten by mistake when adding more domains. Should we have a different strategy to merge directives so that we always include Shopify defaults?
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.
Yeah I went back and forth on that. I decided to keep make sure the nonce is always merged in, because that's the main value prop we are providing. So if someone doesn't want the nonce, they should just do their own CSP manually. But if we automatically merge domains as well? If they are deploying off oxygen, there's a high chance that they wouldn't want the shopify domains at all.
WHY are these changes introduced?
Hydrogen provides no default content security policy (CSP) implementation at the moment. This means that any site created with the default templates is subject to cross-site-scripting and embedded content vulnerabilities. This adds the following primitives and updates the templates:
createContentSecurityPolicy
- returns a provider, nonce, and header all at once. I think making the provider not directly exposed is better, because it should only ever be rendered inside entry.server.jsx, right where the CSP is created. In fact I could create a warning if we detect createContentSecurityPolicy is called from the browser.useNonce
- This is how you get access to the nonce inside actual react components.<Script>
- The same as a normal <script> except the nonce is automatically added