From ef5540f2781e12b5fda35ed7349f54355e0ee51f Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 2 Dec 2025 09:03:22 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=90=20refactor:=20MCP=20User=20Variabl?= =?UTF-8?q?e=20Description=20Rendering=20(#10769)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Add back user variable descriptions for MCP under input and not as Tooltips - Integrated DOMPurify to sanitize HTML content in user variable descriptions, ensuring safe rendering of links and formatting. - Updated the AuthField component to display sanitized descriptions, enhancing security and user experience. - Removed TooltipAnchor in favor of direct label rendering for improved clarity. * 📦 chore: Update `dompurify` to v3.3.0 in package dependencies - Added `dompurify` version 3.3.0 to `package.json` and `package-lock.json` for improved HTML sanitization. - Updated existing references to `dompurify` to ensure consistency across the project. * refactor: Update tooltip styles for sanitized description in AuthField component --- client/package.json | 1 + .../components/MCP/CustomUserVarsSection.tsx | 54 ++++++++++++++----- package-lock.json | 10 ++-- packages/client/package.json | 2 +- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/client/package.json b/client/package.json index d19d0bfcf8..f3aa22626d 100644 --- a/client/package.json +++ b/client/package.json @@ -64,6 +64,7 @@ "copy-to-clipboard": "^3.3.3", "cross-env": "^7.0.3", "date-fns": "^3.3.1", + "dompurify": "^3.3.0", "downloadjs": "^1.4.7", "export-from-json": "^1.7.2", "filenamify": "^6.0.0", diff --git a/client/src/components/MCP/CustomUserVarsSection.tsx b/client/src/components/MCP/CustomUserVarsSection.tsx index 9c2f0870d4..35e613dd42 100644 --- a/client/src/components/MCP/CustomUserVarsSection.tsx +++ b/client/src/components/MCP/CustomUserVarsSection.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; +import DOMPurify from 'dompurify'; import { useForm, Controller } from 'react-hook-form'; -import { Input, Label, Button, TooltipAnchor, CircleHelpIcon } from '@librechat/client'; +import { Input, Label, Button } from '@librechat/client'; import { useMCPAuthValuesQuery } from '~/data-provider/Tools/queries'; import { useLocalize } from '~/hooks'; @@ -27,21 +28,40 @@ interface AuthFieldProps { function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) { const localize = useLocalize(); + const sanitizer = useMemo(() => { + const instance = DOMPurify(); + instance.addHook('afterSanitizeAttributes', (node) => { + if (node.tagName && node.tagName === 'A') { + node.setAttribute('target', '_blank'); + node.setAttribute('rel', 'noopener noreferrer'); + } + }); + return instance; + }, []); + + const sanitizedDescription = useMemo(() => { + if (!config.description) { + return ''; + } + try { + return sanitizer.sanitize(config.description, { + ALLOWED_TAGS: ['a', 'strong', 'b', 'em', 'i', 'br', 'code'], + ALLOWED_ATTR: ['href', 'class', 'target', 'rel'], + ALLOW_DATA_ATTR: false, + ALLOW_ARIA_ATTR: false, + }); + } catch (error) { + console.error('Sanitization failed', error); + return config.description; + } + }, [config.description, sanitizer]); + return (
- - - -
- } - /> + {hasValue ? (
@@ -66,12 +86,18 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) placeholder={ hasValue ? localize('com_ui_mcp_update_var', { 0: config.title }) - : `${localize('com_ui_mcp_enter_var', { 0: config.title })} ${localize('com_ui_optional')}` + : localize('com_ui_mcp_enter_var', { 0: config.title }) } className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary placeholder:text-text-secondary focus:outline-none sm:text-sm" /> )} /> + {sanitizedDescription && ( +

+ )} {errors[name] &&

{errors[name]?.message}

}
); diff --git a/package-lock.json b/package-lock.json index 30d31c370b..6ece322d7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -484,6 +484,7 @@ "copy-to-clipboard": "^3.3.3", "cross-env": "^7.0.3", "date-fns": "^3.3.1", + "dompurify": "^3.3.0", "downloadjs": "^1.4.7", "export-from-json": "^1.7.2", "filenamify": "^6.0.0", @@ -28441,11 +28442,10 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -46398,7 +46398,7 @@ "@tanstack/react-virtual": "^3.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dompurify": "^3.2.6", + "dompurify": "^3.3.0", "framer-motion": "^12.23.6", "i18next": "^24.2.2 || ^25.3.2", "i18next-browser-languagedetector": "^8.2.0", diff --git a/packages/client/package.json b/packages/client/package.json index 1c5abcfe85..8d395e9f89 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -51,7 +51,7 @@ "@tanstack/react-virtual": "^3.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dompurify": "^3.2.6", + "dompurify": "^3.3.0", "framer-motion": "^12.23.6", "i18next": "^24.2.2 || ^25.3.2", "i18next-browser-languagedetector": "^8.2.0",