diff --git a/biome.json b/biome.json
index 31016231..2d8ece07 100644
--- a/biome.json
+++ b/biome.json
@@ -22,6 +22,9 @@
"noNonNullAssertion": "off",
"noParameterAssign": "off",
"useTemplate": "off"
+ },
+ "security": {
+ "noDangerouslySetInnerHtml": "off"
}
},
"ignore": ["client/browser/src/types/webextension-polyfill/index.d.ts"]
diff --git a/lib/ui-react/package.json b/lib/ui-react/package.json
index 360b3ff0..8c5d6ea2 100644
--- a/lib/ui-react/package.json
+++ b/lib/ui-react/package.json
@@ -11,7 +11,14 @@
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
- "files": ["dist", "src", "!**/*.test.*", "!**/*.story.*", "!dist/**/*.ts?(x)", "dist/**/*.d.ts"],
+ "files": [
+ "dist",
+ "src",
+ "!**/*.test.*",
+ "!**/*.story.*",
+ "!dist/**/*.ts?(x)",
+ "dist/**/*.d.ts"
+ ],
"sideEffects": false,
"scripts": {
"prebuild": "mkdir -p dist && cp -R src/* dist/ && find dist/ -name '*.tsx' -delete",
@@ -24,9 +31,11 @@
"@openctx/schema": "workspace:*",
"@openctx/ui-common": "workspace:*",
"@openctx/ui-standalone": "workspace:*",
- "clsx": "^2.1.0"
+ "clsx": "^2.1.0",
+ "dompurify": "^3.1.6"
},
"devDependencies": {
+ "@types/dompurify": "^3.0.5",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"react": "^18.2.0",
diff --git a/lib/ui-react/src/chip/Chip.tsx b/lib/ui-react/src/chip/Chip.tsx
index 89b4de8c..50b883a7 100644
--- a/lib/ui-react/src/chip/Chip.tsx
+++ b/lib/ui-react/src/chip/Chip.tsx
@@ -1,8 +1,8 @@
import type { Annotation } from '@openctx/schema'
import { renderHoverToHTML } from '@openctx/ui-common'
+import DOMPurify from 'dompurify'
import type { FunctionComponent } from 'react'
import { BaseChip } from './BaseChip.js'
-
/**
* A single OpenCtx annotation, displayed as a "chip".
*/
@@ -22,8 +22,9 @@ export const Chip: FunctionComponent<{
renderedHover.format === 'text' ? (
{renderedHover.value}
) : (
- // biome-ignore lint/security/noDangerouslySetInnerHtml: input is sanitized by renderHoverToHTML
-
+
)
) : null
}
diff --git a/package.json b/package.json
index 008f3d46..c8be9d69 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,10 @@
"@storybook/html-vite": "^7.6.7",
"@storybook/react": "^7.6.7",
"@storybook/react-vite": "^8.1.1",
+ "@types/dompurify": "^3.0.5",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20",
+ "@types/picomatch": "^2.3.4",
"@types/semver": "^7.5.6",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.6.0",
@@ -49,7 +51,9 @@
"vitest": "^1.6.0"
},
"stylelint": {
- "extends": ["./.config/stylelintrc.json"]
+ "extends": [
+ "./.config/stylelintrc.json"
+ ]
},
"pnpm": {
"packageExtensions": {
@@ -61,3 +65,4 @@
}
}
}
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 522cbb0c..7aeae787 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -28,12 +28,18 @@ importers:
'@storybook/react-vite':
specifier: ^8.1.1
version: 8.1.1(prettier@3.2.5)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5)(vite@5.2.11)
+ '@types/dompurify':
+ specifier: ^3.0.5
+ version: 3.0.5
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
'@types/node':
specifier: ^20
version: 20.10.0
+ '@types/picomatch':
+ specifier: ^2.3.4
+ version: 2.3.4
'@types/semver':
specifier: ^7.5.6
version: 7.5.6
@@ -469,7 +475,13 @@ importers:
clsx:
specifier: ^2.1.0
version: 2.1.0
+ dompurify:
+ specifier: ^3.1.6
+ version: 3.1.6
devDependencies:
+ '@types/dompurify':
+ specifier: ^3.0.5
+ version: 3.0.5
'@types/react':
specifier: 18.2.37
version: 18.2.37
@@ -813,6 +825,9 @@ importers:
compression:
specifier: ^1.7.4
version: 1.7.4
+ dompurify:
+ specifier: ^3.1.6
+ version: 3.1.6
express:
specifier: ^4.18.2
version: 4.18.2
@@ -861,6 +876,10 @@ importers:
vite:
specifier: ^5.0.11
version: 5.1.4(@types/node@20.11.20)
+ devDependencies:
+ '@types/dompurify':
+ specifier: ^3.0.5
+ version: 3.0.5
packages:
@@ -4286,7 +4305,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
@@ -4648,7 +4667,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@floating-ui/react-dom': 2.0.4(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
@@ -4760,7 +4779,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
@@ -4954,7 +4973,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
react: 18.2.0
@@ -4994,7 +5013,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.37
react: 18.2.0
@@ -5008,7 +5027,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
react: 18.2.0
@@ -5036,7 +5055,7 @@ packages:
/@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
/@rollup/pluginutils@4.2.1:
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
@@ -6668,6 +6687,12 @@ packages:
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
dev: true
+ /@types/dompurify@3.0.5:
+ resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
+ dependencies:
+ '@types/trusted-types': 2.0.7
+ dev: true
+
/@types/ejs@3.1.2:
resolution: {integrity: sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==}
dev: true
@@ -6865,6 +6890,10 @@ packages:
resolution: {integrity: sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==}
dev: true
+ /@types/picomatch@2.3.4:
+ resolution: {integrity: sha512-0so8lU8O5zatZS/2Fi4zrwks+vZv7e0dygrgEZXljODXBig97l4cPQD+9LabXfGJOWwoRkTVz6Q4edZvD12UOA==}
+ dev: true
+
/@types/prettier@2.7.3:
resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
dev: true
@@ -6937,6 +6966,10 @@ packages:
'@types/node': 20.11.20
dev: true
+ /@types/trusted-types@2.0.7:
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+ dev: true
+
/@types/tsscmp@1.0.2:
resolution: {integrity: sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g==}
dev: false
@@ -8393,7 +8426,7 @@ packages:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
- '@babel/runtime': 7.23.5
+ '@babel/runtime': 7.23.9
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
@@ -8733,6 +8766,10 @@ packages:
dependencies:
domelementtype: 2.3.0
+ /dompurify@3.1.6:
+ resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==}
+ dev: false
+
/domutils@3.1.0:
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
dependencies:
diff --git a/web/package.json b/web/package.json
index b84a0915..d06fc4b3 100644
--- a/web/package.json
+++ b/web/package.json
@@ -46,6 +46,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"compression": "^1.7.4",
+ "dompurify": "^3.1.6",
"express": "^4.18.2",
"lucide-react": "^0.294.0",
"postcss": "^8.4.33",
@@ -63,5 +64,8 @@
"vike-react": "^0.4.6",
"vite": "^5.0.11"
},
- "version": "0.0.6"
-}
+ "version": "0.0.6",
+ "devDependencies": {
+ "@types/dompurify": "^3.0.5"
+ }
+}
\ No newline at end of file
diff --git a/web/src/content/ContentPage.tsx b/web/src/content/ContentPage.tsx
index c84526ac..4a970d2a 100644
--- a/web/src/content/ContentPage.tsx
+++ b/web/src/content/ContentPage.tsx
@@ -1,3 +1,4 @@
+import DOMPurify from 'dompurify'
import type { FunctionComponent } from 'react'
import { usePageContext } from 'vike-react/usePageContext'
import type { PageContext } from 'vike/types'
@@ -19,8 +20,11 @@ export const ContentPage: FunctionComponent<{ content: ContentPages }> = ({ cont
) : pageContext.contentPageHtml ? (
- // biome-ignore lint/security/noDangerouslySetInnerHtml: The input value does not come from the user.
-
+
) : null}
)
diff --git a/web/src/content/contentPages.tsx b/web/src/content/contentPages.tsx
index a522b67c..0a697157 100644
--- a/web/src/content/contentPages.tsx
+++ b/web/src/content/contentPages.tsx
@@ -1,4 +1,5 @@
import { MDXProvider } from '@mdx-js/react'
+import DOMPurify from 'dompurify'
import { type ComponentType, useEffect, useState } from 'react'
import { renderToString } from 'react-dom/server'
import { render } from 'vike/abort'
@@ -79,10 +80,13 @@ export function createOnBeforeRender(content: ContentPages): OnBeforeRenderAsync
contentPageComponent: MDXContent,
contentPageHtml:
typeof window === 'undefined'
- ? renderToString(
-
-
- ,
+ ? DOMPurify.sanitize(
+ renderToString(
+
+
+ ,
+ ),
+ { RETURN_TRUSTED_TYPE: true },
)
: undefined,
contentPageInfos: infos,