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",