diff --git a/package-lock.json b/package-lock.json index 28a43d1e4b..50e6e7ee1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "clipboard-copy": "4.0.1", "clsx": "1.1.1", "dayjs": "1.11.13", + "dompurify": "3.2.6", "dropbox": "10.7.0", "focus-visible": "5.1.0", "glob": "11.0.3", @@ -7957,6 +7958,13 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unorm": { "version": "1.3.28", "resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz", @@ -12437,6 +12445,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -32389,6 +32406,12 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "@types/unorm": { "version": "1.3.28", "resolved": "https://registry.npmjs.org/@types/unorm/-/unorm-1.3.28.tgz", @@ -35531,6 +35554,14 @@ "domelementtype": "^2.2.0" } }, + "dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "requires": { + "@types/trusted-types": "^2.0.7" + } + }, "domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", diff --git a/package.json b/package.json index baaf772fc4..0fd9c668b4 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "clipboard-copy": "4.0.1", "clsx": "1.1.1", "dayjs": "1.11.13", + "dompurify": "3.2.6", "dropbox": "10.7.0", "focus-visible": "5.1.0", "glob": "11.0.3", diff --git a/react/features/dynamic-branding/functions.any.ts b/react/features/dynamic-branding/functions.any.ts index 95a6d744d9..e7544ade37 100644 --- a/react/features/dynamic-branding/functions.any.ts +++ b/react/features/dynamic-branding/functions.any.ts @@ -2,6 +2,7 @@ import { IReduxState } from '../app/types'; import { IStateful } from '../base/app/types'; import { toState } from '../base/redux/functions'; +import { cleanSvg } from './functions'; import logger from './logger'; /** @@ -82,7 +83,7 @@ export const fetchCustomIcons = async (customIcons: Record) => { if (response.ok) { const svgXml = await response.text(); - localCustomIcons[key] = svgXml; + localCustomIcons[key] = cleanSvg(svgXml); } else { logger.error(`Failed to fetch ${url}. Status: ${response.status}`); } diff --git a/react/features/dynamic-branding/functions.native.ts b/react/features/dynamic-branding/functions.native.ts new file mode 100644 index 0000000000..c6866ce158 --- /dev/null +++ b/react/features/dynamic-branding/functions.native.ts @@ -0,0 +1,9 @@ +/** + * Sanitizes the given SVG by removing dangerous elements. + * + * @param {string} svg - The SVG string to clean. + * @returns {string} The sanitized SVG string. + */ +export function cleanSvg(svg: string): string { + return svg; +} diff --git a/react/features/dynamic-branding/functions.web.ts b/react/features/dynamic-branding/functions.web.ts index 6923c2f9b6..338ca97509 100644 --- a/react/features/dynamic-branding/functions.web.ts +++ b/react/features/dynamic-branding/functions.web.ts @@ -1,11 +1,22 @@ import { Theme } from '@mui/material'; import { adaptV4Theme, createTheme } from '@mui/material/styles'; +import DOMPurify from 'dompurify'; import { breakpoints, colorMap, font, shape, spacing, typography } from '../base/ui/Tokens'; import { createColorTokens } from '../base/ui/utils'; const DEFAULT_FONT_SIZE = 16; +/** + * Sanitizes the given SVG by removing dangerous elements. + * + * @param {string} svg - The SVG string to clean. + * @returns {string} The sanitized SVG string. + */ +export function cleanSvg(svg: string): string { + return DOMPurify.sanitize(svg); +} + /** * Converts unitless fontSize and lineHeight values in a typography style object to rem units. * Backward compatibility: This conversion supports custom themes that may still override