From 4d6a530a015bbc08ecede8d99d10756a63b97e11 Mon Sep 17 00:00:00 2001 From: Bret Little Date: Tue, 12 Mar 2024 11:48:41 -0400 Subject: [PATCH 1/2] Fix xss vulnerability in the seo component --- .changeset/popular-moose-beam.md | 5 + package-lock.json | 197 +++++++++++++++++- packages/hydrogen/package.json | 6 +- .../hydrogen/src/seo/generate-seo-tags.ts | 6 +- packages/hydrogen/src/seo/seo.test.ts | 29 +++ 5 files changed, 232 insertions(+), 11 deletions(-) create mode 100644 .changeset/popular-moose-beam.md diff --git a/.changeset/popular-moose-beam.md b/.changeset/popular-moose-beam.md new file mode 100644 index 0000000000..cb00e29901 --- /dev/null +++ b/.changeset/popular-moose-beam.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen': patch +--- + +Fix XSS vulnerability in the SEO component diff --git a/package-lock.json b/package-lock.json index 05adcd5095..b7d2a4f0bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11982,6 +11982,15 @@ "@types/node": "*" } }, + "node_modules/@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, "node_modules/@types/scheduler": { "version": "0.16.2", "devOptional": true, @@ -15600,6 +15609,57 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-case": { "version": "3.0.4", "license": "MIT", @@ -15775,6 +15835,17 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -18819,6 +18890,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-assert": { "version": "1.5.0", "dev": true, @@ -22898,7 +22987,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -23926,6 +24014,11 @@ "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "node_modules/parse5": { "version": "6.0.1", "dev": true, @@ -24379,7 +24472,6 @@ "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -26590,6 +26682,19 @@ "version": "2.1.2", "license": "MIT" }, + "node_modules/sanitize-html": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", + "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, "node_modules/scheduler": { "version": "0.23.0", "license": "MIT", @@ -27129,7 +27234,6 @@ }, "node_modules/source-map-js": { "version": "1.0.2", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -30841,6 +30945,7 @@ "dependencies": { "@shopify/hydrogen-react": "2024.1.1", "content-security-policy-builder": "^2.1.1", + "sanitize-html": "^2.3.0", "type-fest": "^4.5.0" }, "devDependencies": { @@ -30849,6 +30954,7 @@ "@shopify/generate-docs": "0.11.1", "@shopify/hydrogen-codegen": "*", "@testing-library/react": "^14.0.0", + "@types/sanitize-html": "2.11.0", "happy-dom": "^8.9.0", "react": "^18.2.0", "schema-dts": "^1.1.0", @@ -38515,9 +38621,11 @@ "@shopify/hydrogen-codegen": "*", "@shopify/hydrogen-react": "2024.1.1", "@testing-library/react": "^14.0.0", + "@types/sanitize-html": "2.11.0", "content-security-policy-builder": "^2.1.1", "happy-dom": "^8.9.0", "react": "^18.2.0", + "sanitize-html": "^2.3.0", "schema-dts": "^1.1.0", "type-fest": "^4.5.0", "vitest": "^1.0.4" @@ -39481,6 +39589,15 @@ "@types/node": "*" } }, + "@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "requires": { + "htmlparser2": "^8.0.0" + } + }, "@types/scheduler": { "version": "0.16.2", "devOptional": true @@ -41876,6 +41993,39 @@ "version": "0.5.14", "dev": true }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dot-case": { "version": "3.0.4", "requires": { @@ -42007,6 +42157,11 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -44135,6 +44290,17 @@ "version": "2.0.1", "dev": true }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "http-assert": { "version": "1.5.0", "dev": true, @@ -46696,8 +46862,7 @@ "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -47364,6 +47529,11 @@ "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "parse5": { "version": "6.0.1", "dev": true @@ -47683,7 +47853,6 @@ "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -49003,6 +49172,19 @@ "safer-buffer": { "version": "2.1.2" }, + "sanitize-html": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", + "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "requires": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, "scheduler": { "version": "0.23.0", "requires": { @@ -49426,8 +49608,7 @@ "version": "0.7.4" }, "source-map-js": { - "version": "1.0.2", - "dev": true + "version": "1.0.2" }, "source-map-support": { "version": "0.5.21", diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index 24e78ca08b..8979a2e86b 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -61,7 +61,8 @@ "dependencies": { "@shopify/hydrogen-react": "2024.1.1", "content-security-policy-builder": "^2.1.1", - "type-fest": "^4.5.0" + "type-fest": "^4.5.0", + "sanitize-html": "^2.3.0" }, "peerDependencies": { "@remix-run/react": "^2.1.0", @@ -77,6 +78,7 @@ "happy-dom": "^8.9.0", "react": "^18.2.0", "schema-dts": "^1.1.0", - "vitest": "^1.0.4" + "vitest": "^1.0.4", + "@types/sanitize-html": "2.11.0" } } diff --git a/packages/hydrogen/src/seo/generate-seo-tags.ts b/packages/hydrogen/src/seo/generate-seo-tags.ts index 8b6f606f62..2d8ae27a57 100644 --- a/packages/hydrogen/src/seo/generate-seo-tags.ts +++ b/packages/hydrogen/src/seo/generate-seo-tags.ts @@ -2,6 +2,8 @@ import type {ComponentPropsWithoutRef} from 'react'; import type {Maybe} from '@shopify/hydrogen-react/storefront-api-types'; import type {Thing, WithContext} from 'schema-dts'; +import sanitizeHtml from 'sanitize-html'; + const ERROR_PREFIX = 'Error in SEO input: '; // TODO: Refactor this into more reusable validators or use a library like zod to do this if we decide to use it in @@ -503,7 +505,9 @@ export function generateSeoTags< 'script', { type: 'application/ld+json', - children: JSON.stringify(block), + children: JSON.stringify(block, (k, value) => { + return typeof value === 'string' ? sanitizeHtml(value) : value; + }), }, // @ts-expect-error `json-ld-${block?.['@type'] || block?.name || index++}`, diff --git a/packages/hydrogen/src/seo/seo.test.ts b/packages/hydrogen/src/seo/seo.test.ts index f3670bad3d..469b836dea 100644 --- a/packages/hydrogen/src/seo/seo.test.ts +++ b/packages/hydrogen/src/seo/seo.test.ts @@ -264,6 +264,35 @@ describe('seo', () => { `); }); + + it.only('escapes script content', async () => { + vi.mocked(useMatches).mockReturnValueOnce([ + fillMatch({ + data: { + seo: { + jsonLd: { + '@context': 'https://schema.org', + '@type': 'Organization', + name: 'Hydrogen Root', + description: 'shows up', + }, + }, + }, + }), + ]); + + const {asFragment} = render(createElement(Seo)); + + expect(asFragment()).toMatchInlineSnapshot(` + + + + `); + }); }); function fillMatch(partial: Partial> = {}) { From 08e70e6406d4b9dda88f5710dc4a272616324c3f Mon Sep 17 00:00:00 2001 From: Bret Little Date: Tue, 12 Mar 2024 13:34:43 -0400 Subject: [PATCH 2/2] Switch to xss as a dep --- package-lock.json | 249 ++++-------------- packages/hydrogen/package.json | 5 +- .../hydrogen/src/seo/generate-seo-tags.ts | 9 +- packages/hydrogen/src/seo/seo.test.ts | 2 +- 4 files changed, 68 insertions(+), 197 deletions(-) diff --git a/package-lock.json b/package-lock.json index b7d2a4f0bd..7ae516d7f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11982,15 +11982,6 @@ "@types/node": "*" } }, - "node_modules/@types/sanitize-html": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", - "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", - "dev": true, - "dependencies": { - "htmlparser2": "^8.0.0" - } - }, "node_modules/@types/scheduler": { "version": "0.16.2", "devOptional": true, @@ -15115,6 +15106,11 @@ "node": ">=4" } }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" + }, "node_modules/csstype": { "version": "3.1.1", "devOptional": true, @@ -15609,57 +15605,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dot-case": { "version": "3.0.4", "license": "MIT", @@ -15835,17 +15780,6 @@ "node": ">=8.6" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -18890,24 +18824,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/http-assert": { "version": "1.5.0", "dev": true, @@ -22987,6 +22903,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", @@ -24014,11 +23931,6 @@ "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, "node_modules/parse5": { "version": "6.0.1", "dev": true, @@ -24472,6 +24384,7 @@ "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -26682,19 +26595,6 @@ "version": "2.1.2", "license": "MIT" }, - "node_modules/sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, "node_modules/scheduler": { "version": "0.23.0", "license": "MIT", @@ -27234,6 +27134,7 @@ }, "node_modules/source-map-js": { "version": "1.0.2", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -30333,6 +30234,26 @@ } } }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/xtend": { "version": "4.0.2", "license": "MIT", @@ -30945,8 +30866,8 @@ "dependencies": { "@shopify/hydrogen-react": "2024.1.1", "content-security-policy-builder": "^2.1.1", - "sanitize-html": "^2.3.0", - "type-fest": "^4.5.0" + "type-fest": "^4.5.0", + "xss": "^1.0.15" }, "devDependencies": { "@remix-run/react": "^2.8.0", @@ -30954,7 +30875,6 @@ "@shopify/generate-docs": "0.11.1", "@shopify/hydrogen-codegen": "*", "@testing-library/react": "^14.0.0", - "@types/sanitize-html": "2.11.0", "happy-dom": "^8.9.0", "react": "^18.2.0", "schema-dts": "^1.1.0", @@ -38621,14 +38541,13 @@ "@shopify/hydrogen-codegen": "*", "@shopify/hydrogen-react": "2024.1.1", "@testing-library/react": "^14.0.0", - "@types/sanitize-html": "2.11.0", "content-security-policy-builder": "^2.1.1", "happy-dom": "^8.9.0", "react": "^18.2.0", - "sanitize-html": "^2.3.0", "schema-dts": "^1.1.0", "type-fest": "^4.5.0", - "vitest": "^1.0.4" + "vitest": "^1.0.4", + "xss": "^1.0.15" }, "dependencies": { "type-fest": { @@ -39589,15 +39508,6 @@ "@types/node": "*" } }, - "@types/sanitize-html": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", - "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", - "dev": true, - "requires": { - "htmlparser2": "^8.0.0" - } - }, "@types/scheduler": { "version": "0.16.2", "devOptional": true @@ -41657,6 +41567,11 @@ "version": "3.0.0", "dev": true }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" + }, "csstype": { "version": "3.1.1", "devOptional": true @@ -41993,39 +41908,6 @@ "version": "0.5.14", "dev": true }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, "dot-case": { "version": "3.0.4", "requires": { @@ -42157,11 +42039,6 @@ "ansi-colors": "^4.1.1" } }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, "env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -44290,17 +44167,6 @@ "version": "2.0.1", "dev": true }, - "htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "http-assert": { "version": "1.5.0", "dev": true, @@ -46862,7 +46728,8 @@ "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -47529,11 +47396,6 @@ "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==" }, - "parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, "parse5": { "version": "6.0.1", "dev": true @@ -47853,6 +47715,7 @@ "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -49172,19 +49035,6 @@ "safer-buffer": { "version": "2.1.2" }, - "sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", - "requires": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, "scheduler": { "version": "0.23.0", "requires": { @@ -49608,7 +49458,8 @@ "version": "0.7.4" }, "source-map-js": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "source-map-support": { "version": "0.5.21", @@ -51552,6 +51403,22 @@ "version": "7.5.9", "requires": {} }, + "xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "requires": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, "xtend": { "version": "4.0.2" }, diff --git a/packages/hydrogen/package.json b/packages/hydrogen/package.json index 8979a2e86b..367e505f32 100644 --- a/packages/hydrogen/package.json +++ b/packages/hydrogen/package.json @@ -62,7 +62,7 @@ "@shopify/hydrogen-react": "2024.1.1", "content-security-policy-builder": "^2.1.1", "type-fest": "^4.5.0", - "sanitize-html": "^2.3.0" + "xss": "^1.0.15" }, "peerDependencies": { "@remix-run/react": "^2.1.0", @@ -78,7 +78,6 @@ "happy-dom": "^8.9.0", "react": "^18.2.0", "schema-dts": "^1.1.0", - "vitest": "^1.0.4", - "@types/sanitize-html": "2.11.0" + "vitest": "^1.0.4" } } diff --git a/packages/hydrogen/src/seo/generate-seo-tags.ts b/packages/hydrogen/src/seo/generate-seo-tags.ts index 2d8ae27a57..04ce2935ba 100644 --- a/packages/hydrogen/src/seo/generate-seo-tags.ts +++ b/packages/hydrogen/src/seo/generate-seo-tags.ts @@ -2,7 +2,7 @@ import type {ComponentPropsWithoutRef} from 'react'; import type {Maybe} from '@shopify/hydrogen-react/storefront-api-types'; import type {Thing, WithContext} from 'schema-dts'; -import sanitizeHtml from 'sanitize-html'; +import xss from 'xss'; const ERROR_PREFIX = 'Error in SEO input: '; @@ -506,7 +506,12 @@ export function generateSeoTags< { type: 'application/ld+json', children: JSON.stringify(block, (k, value) => { - return typeof value === 'string' ? sanitizeHtml(value) : value; + return typeof value === 'string' + ? xss(value, { + stripIgnoreTag: true, + stripIgnoreTagBody: true, + }) + : value; }), }, // @ts-expect-error diff --git a/packages/hydrogen/src/seo/seo.test.ts b/packages/hydrogen/src/seo/seo.test.ts index 469b836dea..b32a96d616 100644 --- a/packages/hydrogen/src/seo/seo.test.ts +++ b/packages/hydrogen/src/seo/seo.test.ts @@ -265,7 +265,7 @@ describe('seo', () => { `); }); - it.only('escapes script content', async () => { + it('escapes script content', async () => { vi.mocked(useMatches).mockReturnValueOnce([ fillMatch({ data: {