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: {}