From 67aeda9745171fe138ae485efc8db54b9adf6c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dcaro=20Azevedo?= Date: Wed, 4 Oct 2023 18:16:33 -0300 Subject: [PATCH 1/5] feat(api): sanitize product description --- packages/api/package.json | 4 +- .../src/platforms/vtex/utils/enhanceSku.ts | 12 +- .../src/platforms/vtex/utils/sanitizeHtml.ts | 109 ++++++++++++++++++ yarn.lock | 69 ++++++++++- 4 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 packages/api/src/platforms/vtex/utils/sanitizeHtml.ts diff --git a/packages/api/package.json b/packages/api/package.json index 23036dc22d..99b3c45d5e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -32,9 +32,11 @@ "dataloader": "^2.1.0", "fast-deep-equal": "^3.1.3", "isomorphic-unfetch": "^3.1.0", - "p-limit": "^3.1.0" + "p-limit": "^3.1.0", + "sanitize-html": "^2.11.0" }, "devDependencies": { + "@types/sanitize-html": "^2.9.1", "@envelop/core": "^2.6.0", "@faststore/eslint-config": "^2.1.105", "@faststore/shared": "^2.1.105", diff --git a/packages/api/src/platforms/vtex/utils/enhanceSku.ts b/packages/api/src/platforms/vtex/utils/enhanceSku.ts index db351b25b8..01cfa928bc 100644 --- a/packages/api/src/platforms/vtex/utils/enhanceSku.ts +++ b/packages/api/src/platforms/vtex/utils/enhanceSku.ts @@ -1,8 +1,18 @@ import type { Product, Item } from '../clients/search/types/ProductSearchResult' +import { sanitizeHtml } from './sanitizeHtml' export type EnhancedSku = Item & { isVariantOf: Product } +function sanitizeProduct(product: Product): Product { + return { + ...product, + description: product.description + ? sanitizeHtml(product.description) + : product.description, + } +} + export const enhanceSku = (item: Item, product: Product): EnhancedSku => ({ ...item, - isVariantOf: product, + isVariantOf: sanitizeProduct(product), }) diff --git a/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts b/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts new file mode 100644 index 0000000000..30b96909d8 --- /dev/null +++ b/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts @@ -0,0 +1,109 @@ +import sanitizeHtmlLib from 'sanitize-html' + +/** + * This list was taken from Store Framework's Product Description app. + * https://github.com/vtex-apps/store-components/blob/master/react/ProductDescription.tsx + * + * This is a time-tested set of options that already works with the whole customer base of VTEX + * customers using Store Framework. + */ +const allowedTags = [ + 'a', + 'abbr', + 'article', + 'b', + 'blockquote', + 'br', + 'caption', + 'code', + 'del', + 'details', + 'div', + 'em', + 'figure', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'header', + 'footer', + 'i', + 'img', + 'ins', + 'iframe', + 'kbd', + 'li', + 'main', + 'mark', + 'ol', + 'p', + 'picture', + 'pre', + 'section', + 'source', + 'span', + 'strike', + 'strong', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'th', + 'thead', + 'tr', + 'u', + 'ul', + 'link', + 'body', + 'html', + 'style', + 'link', + 'script', + 'head', + 'meta', + 'object', + 'embed', +] + +const allowedAttributes = { + '*': [ + 'id', + 'title', + 'accesskey', + 'class', + 'style', + 'aria-label', + 'width', + 'height', + 'hidden', + ], + a: ['href', 'name', 'target'], + iframe: ['allow', 'allowfullscreen', 'frameborder', 'src'], + img: ['src', 'alt'], + link: ['rel', 'type', 'href'], + td: ['colspan', 'rowspan', 'headers'], + meta: ['charset', 'name', 'content'], + object: ['type', 'height', 'width', 'data'], + embed: ['height', 'width', 'src'], +} + +const allowedSchemes = ['http', 'https', 'mailto', 'tel'] + +const allowedClasses = {} + +export const sanitizeHtml = ( + dirty: Parameters[0], + options?: Parameters[1] +) => + sanitizeHtmlLib(dirty, { + ...(options ?? {}), + allowedTags, + allowedAttributes, + allowedClasses, + allowedSchemes, + }) diff --git a/yarn.lock b/yarn.lock index c830f42fc9..d52c91cd28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6038,6 +6038,13 @@ dependencies: "@types/node" "*" +"@types/sanitize-html@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.9.1.tgz#6e4b232916cfb3ec0c4733c9899c99e1697ef953" + integrity sha512-XSLD0a9P8c+rKUM09KIi5Nd8mOHLHNgXb1G04rpXWa/GqQVpM+knrS9KR9ptj1CeC3gXWGZn75ApH3H6qNbhYA== + dependencies: + htmlparser2 "^8.0.0" + "@types/scheduler@*": version "0.16.2" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" @@ -10410,6 +10417,15 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -10420,7 +10436,7 @@ domain-browser@^1.1.1: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@^2.0.1, domelementtype@^2.2.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -10439,6 +10455,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + dompurify@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.3.tgz#4b115d15a091ddc96f232bcef668550a2f6f1430" @@ -10453,6 +10476,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -10676,7 +10708,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.4.0: +entities@^4.2.0, entities@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -13393,6 +13425,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -18767,6 +18809,11 @@ parse-path@^7.0.0: dependencies: protocols "^2.0.0" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== + parse-url@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" @@ -19280,9 +19327,9 @@ prettier@^2.2.0: integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== prettier@latest: - version "2.8.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" - integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== pretty-bytes@^5.3.0, pretty-bytes@^5.6.0: version "5.6.0" @@ -20670,6 +20717,18 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sanitize-html@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6" + integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA== + 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" + sass-loader@^12.6.0: version "12.6.0" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" From de7dc794ba6c9cc29725a2b71a7423ece7ec7005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dcaro=20Azevedo?= Date: Wed, 4 Oct 2023 18:17:20 -0300 Subject: [PATCH 2/5] fix identation --- packages/api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/package.json b/packages/api/package.json index 99b3c45d5e..fc68719aa9 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -36,7 +36,7 @@ "sanitize-html": "^2.11.0" }, "devDependencies": { - "@types/sanitize-html": "^2.9.1", + "@types/sanitize-html": "^2.9.1", "@envelop/core": "^2.6.0", "@faststore/eslint-config": "^2.1.105", "@faststore/shared": "^2.1.105", From 92d71c37bc80e34f5b6ed51fe0d974dd48a38134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dcaro=20Azevedo?= Date: Thu, 5 Oct 2023 16:38:26 -0300 Subject: [PATCH 3/5] adjust solution --- .../src/platforms/vtex/utils/sanitizeHtml.ts | 114 +++--------------- .../ProductDescription/ProductDescription.tsx | 9 +- .../organisms/ProductDetails/styles.scss | 12 ++ 3 files changed, 34 insertions(+), 101 deletions(-) diff --git a/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts b/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts index 30b96909d8..af95b93984 100644 --- a/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts +++ b/packages/api/src/platforms/vtex/utils/sanitizeHtml.ts @@ -1,109 +1,23 @@ import sanitizeHtmlLib from 'sanitize-html' + /** - * This list was taken from Store Framework's Product Description app. - * https://github.com/vtex-apps/store-components/blob/master/react/ProductDescription.tsx + * For now, we're using sanitize-html's default set + * of allowed tags and attributes, which don't even include img elements + * + * It is known many client depends on pontentially vulnerable tags, such as script tags + * We chose to be restrictive at first, and document those restrictions later. + * + * When expanding the set of allowed tags and attributes, please consider performance, privacy and security. + * + * This possibily breaks compatibility with Portal and Store Framework, + * which both allows an enormous amount of tags and attributes * - * This is a time-tested set of options that already works with the whole customer base of VTEX - * customers using Store Framework. + * This was a thoughtful decision that can be reviewed in the future given + * research was made to back up those changes. */ -const allowedTags = [ - 'a', - 'abbr', - 'article', - 'b', - 'blockquote', - 'br', - 'caption', - 'code', - 'del', - 'details', - 'div', - 'em', - 'figure', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'hr', - 'header', - 'footer', - 'i', - 'img', - 'ins', - 'iframe', - 'kbd', - 'li', - 'main', - 'mark', - 'ol', - 'p', - 'picture', - 'pre', - 'section', - 'source', - 'span', - 'strike', - 'strong', - 'sub', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'th', - 'thead', - 'tr', - 'u', - 'ul', - 'link', - 'body', - 'html', - 'style', - 'link', - 'script', - 'head', - 'meta', - 'object', - 'embed', -] - -const allowedAttributes = { - '*': [ - 'id', - 'title', - 'accesskey', - 'class', - 'style', - 'aria-label', - 'width', - 'height', - 'hidden', - ], - a: ['href', 'name', 'target'], - iframe: ['allow', 'allowfullscreen', 'frameborder', 'src'], - img: ['src', 'alt'], - link: ['rel', 'type', 'href'], - td: ['colspan', 'rowspan', 'headers'], - meta: ['charset', 'name', 'content'], - object: ['type', 'height', 'width', 'data'], - embed: ['height', 'width', 'src'], -} - -const allowedSchemes = ['http', 'https', 'mailto', 'tel'] - -const allowedClasses = {} - export const sanitizeHtml = ( dirty: Parameters[0], options?: Parameters[1] ) => - sanitizeHtmlLib(dirty, { - ...(options ?? {}), - allowedTags, - allowedAttributes, - allowedClasses, - allowedSchemes, - }) + sanitizeHtmlLib(dirty, options) diff --git a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx index 175128f76b..8795b00141 100644 --- a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx +++ b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx @@ -77,7 +77,14 @@ function ProductDescription({ > {title} -

{content}

+
))} diff --git a/packages/ui/src/components/organisms/ProductDetails/styles.scss b/packages/ui/src/components/organisms/ProductDetails/styles.scss index 85b09c0e78..185308dd45 100644 --- a/packages/ui/src/components/organisms/ProductDetails/styles.scss +++ b/packages/ui/src/components/organisms/ProductDetails/styles.scss @@ -178,6 +178,18 @@ } } + [data-fs-product-details-description-content] { + font-size: var(--fs-text-size-body); + line-height: 1.5; + /** + * display: contents allows you to remove an element from the box tree but still keep its contents + * It doesn't have padding or margin, for example. + * + * https://blogs.igalia.com/mrego/2018/01/11/display-contents-is-coming/ + */ + display: contents; + } + [data-fs-image-gallery="with-selector"] ~ [data-fs-product-description] { @include media(">=notebook") { grid-column: 2 / 9; From f0a961bdd1dd6767e53d89d638f493ad1d35a645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dcaro=20Azevedo?= Date: Thu, 5 Oct 2023 17:28:12 -0300 Subject: [PATCH 4/5] change to single line comments --- .../components/ui/ProductDescription/ProductDescription.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx index 8795b00141..fd180a266a 100644 --- a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx +++ b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx @@ -78,11 +78,9 @@ function ProductDescription({ {title}
From f2b31d822cf4723c3422ed43b6df6f796e475328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dcaro=20Azevedo?= Date: Fri, 6 Oct 2023 20:50:35 +0000 Subject: [PATCH 5/5] Update ProductDescription.tsx --- .../src/components/ui/ProductDescription/ProductDescription.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx index fd180a266a..6511fea741 100644 --- a/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx +++ b/packages/core/src/components/ui/ProductDescription/ProductDescription.tsx @@ -78,7 +78,7 @@ function ProductDescription({ {title}