diff --git a/.changeset/heavy-coins-tickle.md b/.changeset/heavy-coins-tickle.md new file mode 100644 index 0000000000..03b8eb3c96 --- /dev/null +++ b/.changeset/heavy-coins-tickle.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen': minor +--- + +💥 Change the behaviour of `createContentSecurityPolicy` where the custom rules passed in will extends the default Shopify and development domains instead of overriding them. diff --git a/packages/hydrogen/src/csp/csp.test.ts b/packages/hydrogen/src/csp/csp.test.ts index d36e1e75ba..9f2783e90c 100644 --- a/packages/hydrogen/src/csp/csp.test.ts +++ b/packages/hydrogen/src/csp/csp.test.ts @@ -25,28 +25,20 @@ describe('createContentSecurityPolicy', () => { it('adds custom directives', () => { expect( createContentSecurityPolicy({ - styleSrc: [ - "'self'", - 'https://cdn.shopify.com', - 'https://some-custom-css.cdn', - ], + styleSrc: ['https://some-custom-css.cdn'], }).header, ).toBe( - `base-uri 'self'; default-src 'self' 'nonce-somenonce' https://cdn.shopify.com https://shopify.com; frame-ancestors none; style-src 'self' https://cdn.shopify.com https://some-custom-css.cdn; connect-src 'self' https://monorail-edge.shopifysvc.com`, + `base-uri 'self'; default-src 'self' 'nonce-somenonce' https://cdn.shopify.com https://shopify.com; frame-ancestors none; style-src https://some-custom-css.cdn 'self' 'unsafe-inline' https://cdn.shopify.com; connect-src 'self' https://monorail-edge.shopifysvc.com`, ); }); it('adds nonce to custom directives', () => { expect( createContentSecurityPolicy({ - scriptSrc: [ - "'self'", - 'https://cdn.shopify.com', - 'https://some-custom-css.cdn', - ], + scriptSrc: ['https://some-custom-css.cdn'], }).header, ).toBe( - `base-uri 'self'; default-src 'self' 'nonce-somenonce' https://cdn.shopify.com https://shopify.com; frame-ancestors none; style-src 'self' 'unsafe-inline' https://cdn.shopify.com; connect-src 'self' https://monorail-edge.shopifysvc.com; script-src 'self' https://cdn.shopify.com https://some-custom-css.cdn 'nonce-somenonce'`, + `base-uri 'self'; default-src 'self' 'nonce-somenonce' https://cdn.shopify.com https://shopify.com; frame-ancestors none; style-src 'self' 'unsafe-inline' https://cdn.shopify.com; connect-src 'self' https://monorail-edge.shopifysvc.com; script-src https://some-custom-css.cdn 'nonce-somenonce'`, ); }); }); diff --git a/packages/hydrogen/src/csp/csp.ts b/packages/hydrogen/src/csp/csp.ts index 61836fa449..38a0cc6d8e 100644 --- a/packages/hydrogen/src/csp/csp.ts +++ b/packages/hydrogen/src/csp/csp.ts @@ -79,6 +79,16 @@ function createCSPHeader( const combinedDirectives = Object.assign({}, defaultDirectives, directives); + //add defaults if it was override + for (const key in defaultDirectives) { + if (directives[key]) { + combinedDirectives[key] = addCspDirective( + directives[key], + defaultDirectives[key], + ); + } + } + // Make sure that at least script-src includes a nonce directive. // If someone doesn't want a nonce in their CSP, they probably // shouldn't use our utilities and just manually create their CSP. @@ -98,3 +108,19 @@ function createCSPHeader( directives: combinedDirectives, }); } + +function addCspDirective( + currentValue: string[] | string | boolean, + value: string[] | string | boolean, +): boolean | string[] { + const normalizedValue = typeof value === 'string' ? [value] : value; + const normalizedCurrentValue = Array.isArray(currentValue) + ? currentValue + : [String(currentValue)]; + + const newValue = Array.isArray(normalizedValue) + ? [...normalizedCurrentValue, ...normalizedValue] + : normalizedValue; + + return newValue; +}