diff --git a/apps/server/package.json b/apps/server/package.json index 1484f5ff3..f9999b1b4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -34,7 +34,8 @@ "satori": "0.11.2", "sonner": "^1.5.0", "tailwindcss": "3.4.14", - "use-context-selector": "2.0.0" + "use-context-selector": "2.0.0", + "xss": "1.0.15" }, "devDependencies": { "@follow/components": "workspace:*", @@ -45,14 +46,12 @@ "@follow/types": "workspace:*", "@follow/utils": "workspace:*", "@radix-ui/react-avatar": "1.1.1", - "@types/serialize-javascript": "5.0.4", "daisyui": "4.12.13", "foxact": "0.2.39", "lightningcss": "1.27.0", "lodash-es": "4.17.21", "masonic": "4.0.1", "react-dom": "^18.3.1", - "serialize-javascript": "6.0.2", "tailwindcss": "3.4.14", "tsup": "8.3.0", "tsx": "4.19.1", diff --git a/apps/server/src/lib/seo.ts b/apps/server/src/lib/seo.ts index c8552954d..b5bd7525e 100644 --- a/apps/server/src/lib/seo.ts +++ b/apps/server/src/lib/seo.ts @@ -1,18 +1,4 @@ -import serialize from "serialize-javascript" - -function escapeHtml(unsafe?: string | null) { - if (!unsafe) { - return unsafe - } - - return unsafe - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'") - .replaceAll("/", "/") -} +import xss from "xss" export function buildSeoMetaTags(configs: { openGraph: { @@ -22,25 +8,23 @@ export function buildSeoMetaTags(configs: { } }) { const openGraph = { - title: escapeHtml(configs.openGraph.title), - description: escapeHtml(configs.openGraph.description), - image: escapeHtml(configs.openGraph.image), + title: xss(configs.openGraph.title), + description: xss(configs.openGraph.description ?? ""), + image: xss(configs.openGraph.image ?? ""), } const title = `${openGraph.title} | Follow` return [ - ``, + ``, openGraph.description - ? `` + ? `` : "", - openGraph.image ? `` : "", + openGraph.image ? `` : "", // Twitter ``, - ``, + ``, openGraph.description - ? `` - : "", - openGraph.image - ? `` + ? `` : "", + openGraph.image ? `` : "", ].join("\n") } diff --git a/apps/server/src/router/global.ts b/apps/server/src/router/global.ts index d81df3c84..147a3bcd4 100644 --- a/apps/server/src/router/global.ts +++ b/apps/server/src/router/global.ts @@ -6,7 +6,7 @@ import { env } from "@follow/shared/env" import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify" import { parseHTML } from "linkedom" import { FetchError } from "ofetch" -import serialize from "serialize-javascript" +import xss from "xss" import { isDev } from "~/lib/env" import { buildSeoMetaTags } from "~/lib/seo" @@ -119,14 +119,12 @@ async function injectMetaToTemplate(template: string, req: FastifyRequest) { break } case "meta": { - allMetaString.push( - ``, - ) + allMetaString.push(``) break } case "title": { if (meta.title) { - template = template.replace(``, `${serialize(meta.title)} | Follow`) + template = template.replace(``, `${xss(meta.title)} | Follow`) isTitleReplaced = true } break @@ -137,7 +135,7 @@ async function injectMetaToTemplate(template: string, req: FastifyRequest) { const script = document.createElement("script") script.innerHTML = ` window.__HYDRATE__ = window.__HYDRATE__ || {} - window.__HYDRATE__['${meta.key}'] = ${serialize(meta.data)} + window.__HYDRATE__['${meta.key}'] = ${xss(JSON.stringify(meta.data))} ` document.head.append(script) template = document.toString() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f191d4c37..537652e73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -730,6 +730,9 @@ importers: use-context-selector: specifier: 2.0.0 version: 2.0.0(react@18.3.1)(scheduler@0.23.2) + xss: + specifier: 1.0.15 + version: 1.0.15 devDependencies: '@follow/components': specifier: workspace:* @@ -755,9 +758,6 @@ importers: '@radix-ui/react-avatar': specifier: 1.1.1 version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/serialize-javascript': - specifier: 5.0.4 - version: 5.0.4 daisyui: specifier: 4.12.13 version: 4.12.13(patch_hash=igsntdatmoaxzwxof4bkkh35fy)(postcss@8.4.47) @@ -776,9 +776,6 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) - serialize-javascript: - specifier: 6.0.2 - version: 6.0.2 tsup: specifier: 8.3.0 version: 8.3.0(jiti@2.3.3)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.3)(yaml@2.6.0) @@ -4383,9 +4380,6 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/serialize-javascript@5.0.4': - resolution: {integrity: sha512-Z2R7UKFuNWCP8eoa2o9e5rkD3hmWxx/1L0CYz0k2BZzGh0PhEVMp9kfGiqEml/0IglwNERXZ2hwNzIrSz/KHTA==} - '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} @@ -5397,6 +5391,9 @@ packages: engines: {node: '>=4'} hasBin: true + cssfilter@0.0.10: + resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==} + cssnano-preset-default@7.0.6: resolution: {integrity: sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -8992,9 +8989,6 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} @@ -9970,6 +9964,11 @@ packages: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} + xss@1.0.15: + resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} + engines: {node: '>= 0.10.0'} + hasBin: true + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -14284,8 +14283,6 @@ snapshots: '@types/semver@7.5.8': {} - '@types/serialize-javascript@5.0.4': {} - '@types/shimmer@1.2.0': {} '@types/tar@6.1.13': @@ -15555,6 +15552,8 @@ snapshots: cssesc@3.0.0: {} + cssfilter@0.0.10: {} + cssnano-preset-default@7.0.6(postcss@8.4.47): dependencies: browserslist: 4.23.3 @@ -19876,10 +19875,6 @@ snapshots: type-fest: 0.13.1 optional: true - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 - server-only@0.0.1: {} set-blocking@2.0.0: {} @@ -20853,6 +20848,11 @@ snapshots: xmlbuilder@15.1.1: {} + xss@1.0.15: + dependencies: + commander: 2.20.3 + cssfilter: 0.0.10 + xtend@4.0.2: {} y18n@5.0.8: {}