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,