diff --git a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts index ff39a21fa..a3e9e3a80 100644 --- a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts +++ b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts @@ -72,7 +72,11 @@ class Custom_MCP implements INode { label: 'How to use', value: howToUseCode }, - placeholder: mcpServerConfig + placeholder: mcpServerConfig, + warning: + process.env.CUSTOM_MCP_SECURITY_CHECK === 'true' + ? 'In next release, only Remote MCP with url is supported. Read more here' + : undefined }, { label: 'Available Actions', diff --git a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts index 347a35773..610ef5b79 100644 --- a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts +++ b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts @@ -1,7 +1,7 @@ import { Tool } from '@langchain/core/tools' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' import { getNodeModulesPackagePath } from '../../../../src/utils' -import { MCPToolkit, validateMCPServerSecurity } from '../core' +import { MCPToolkit, validateArgsForLocalFileAccess } from '../core' class Supergateway_MCP implements INode { label: string @@ -108,7 +108,7 @@ class Supergateway_MCP implements INode { if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { try { - validateMCPServerSecurity(serverParams) + validateArgsForLocalFileAccess(processedArgs) } catch (error) { throw new Error(`Security validation failed: ${error.message}`) } diff --git a/packages/components/nodes/tools/MCP/core.ts b/packages/components/nodes/tools/MCP/core.ts index be9e65f56..a34d44efa 100644 --- a/packages/components/nodes/tools/MCP/core.ts +++ b/packages/components/nodes/tools/MCP/core.ts @@ -176,375 +176,18 @@ function createSchemaModel( /** * TODO: To be removed and only allow Remote MCP for Cloud - * Validates MCP server configuration to prevent execution of dangerous commands + * Validates MCP server configuration to only allow whitelisted commands */ -export function validateMCPServerSecurity(serverParams: any): void { - // Comprehensive list of dangerous commands that could compromise system security - const dangerousCommands = [ - // Shell interpreters and command processors - 'sh', - 'bash', - 'zsh', - 'fish', - 'csh', - 'tcsh', - 'ksh', - 'ash', - 'dash', - 'cmd', - 'command', - 'powershell', - 'pwsh', - 'cmd.exe', - 'powershell.exe', - 'wsl', - 'wsl.exe', - 'ubuntu', - 'debian', - - // File operations that could read/write sensitive files - 'cat', - 'more', - 'less', - 'head', - 'tail', - 'tee', - 'cp', - 'mv', - 'rm', - 'del', - 'copy', - 'move', - 'type', - 'ren', - 'rename', - 'ln', - 'link', - 'unlink', - 'touch', - 'mkdir', - 'rmdir', - 'rd', - 'md', - 'makedir', - - // Directory operations and file system navigation - 'ls', - 'dir', - 'pwd', - 'cd', - 'find', - 'locate', - 'tree', - 'du', - 'df', - 'pushd', - 'popd', - 'dirs', - 'whereis', - 'which', - 'where', - 'stat', - - // Network operations that could exfiltrate data or download malicious content - 'curl', - 'wget', - 'nc', - 'netcat', - 'ping', - 'nslookup', - 'dig', - 'host', - 'telnet', - 'ssh', - 'scp', - 'rsync', - 'ftp', - 'sftp', - 'icat', - 'socat', - - // System operations and process management - 'ps', - 'top', - 'htop', - 'kill', - 'killall', - 'pkill', - 'pgrep', - 'jobs', - 'systemctl', - 'service', - 'chkconfig', - 'update-rc.d', - 'systemd', - 'crontab', - 'at', - 'batch', - 'nohup', - 'screen', - 'tmux', - - // Archive operations that could be used for data exfiltration - 'tar', - 'zip', - 'unzip', - 'gzip', - 'gunzip', - 'bzip2', - 'bunzip2', - 'xz', - 'unxz', - '7z', - 'rar', - 'unrar', - 'compress', - 'uncompress', - 'cpio', - 'ar', - - // Text processing tools that could read sensitive files - 'grep', - 'awk', - 'sed', - 'sort', - 'uniq', - 'cut', - 'tr', - 'wc', - 'diff', - 'patch', - 'strings', - 'hexdump', - 'od', - 'xxd', - 'base64', - 'base32', - - // File editors that could modify system files - 'vi', - 'vim', - 'nano', - 'emacs', - 'gedit', - 'notepad', - 'notepad.exe', - 'pico', - 'joe', - 'micro', - 'code', - 'subl', - 'atom', - - // Process execution and evaluation commands - 'exec', - 'eval', - 'system', - 'spawn', - 'fork', - 'clone', - 'source', - 'sudo', - 'su', - 'runuser', - 'doas', - 'pfexec', - - // Package managers that could install malicious software - 'npm', - 'yarn', - 'pnpm', - 'pip', - 'pip3', - 'apt', - 'apt-get', - 'yum', - 'dnf', - 'pacman', - 'brew', - 'choco', - 'winget', - 'scoop', - 'snap', - 'flatpak', - - // Compilers and interpreters that could execute arbitrary code - 'python', - 'python3', - 'nodejs', - 'java', - 'javac', - 'gcc', - 'g++', - 'clang', - 'clang++', - 'ruby', - 'perl', - 'php', - 'go', - 'rust', - 'cargo', - 'dotnet', - 'mono', - 'scala', - 'kotlin', - 'swift', - - // Database clients that could access sensitive data - 'mysql', - 'psql', - 'mongo', - 'mongosh', - 'redis-cli', - 'sqlite3', - 'sqlcmd', - 'isql', - 'osql', - 'bcp', - - // Development and deployment tools - 'git', - 'docker', - 'podman', - 'kubectl', - 'helm', - 'terraform', - 'ansible', - 'vagrant', - 'chef', - 'puppet', - 'saltstack', - 'make', - 'cmake', - - // System information and configuration tools - 'uname', - 'whoami', - 'id', - 'groups', - 'env', - 'set', - 'printenv', - 'export', - 'mount', - 'umount', - 'lsblk', - 'fdisk', - 'parted', - 'lsmod', - 'modprobe', - - // File permissions and ownership commands - 'chmod', - 'chown', - 'chgrp', - 'umask', - 'setfacl', - 'getfacl', - 'lsattr', - 'chattr', - - // Network configuration and monitoring - 'ifconfig', - 'ip', - 'route', - 'netstat', - 'ss', - 'lsof', - 'tcpdump', - 'wireshark', - 'iptables', - 'ufw', - 'firewall-cmd', - - // Boot and system control - 'init', - 'telinit', - 'shutdown', - 'reboot', - 'halt', - 'poweroff', - - // Hardware and kernel interaction - 'dmesg', - 'lspci', - 'lsusb', - 'dmidecode', - 'hdparm', - 'smartctl' - ] - - /** - * Checks a string for dangerous commands and patterns - * @param str - The string to check - * @param context - Context information for better error messages - */ - function checkString(str: string, context: string = ''): void { - if (typeof str !== 'string') return - - const lowerStr = str.toLowerCase().trim() - const contextPrefix = context ? `${context}: ` : '' - - for (const cmd of dangerousCommands) { - const cmdLower = cmd.toLowerCase() - // Escape special regex characters in command name - const escapedCmd = cmdLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - - if ( - lowerStr === cmdLower || // 1. Exact match: "cat" === "cat" - lowerStr.startsWith(cmdLower + ' ') || // 2. Command with space args: "cat /etc/passwd" - lowerStr.startsWith(cmdLower + '\t') || // 3. Command with tab args: "cat\t/etc/passwd" - lowerStr.endsWith('/' + cmdLower) || // 4. Unix absolute path: "/bin/sh" - lowerStr.includes('\\' + cmdLower + '.exe') || // 5. Windows executable: "C:\\Windows\\cmd.exe" - lowerStr.includes('/' + cmdLower + ' ') || // 6. Unix path with args: "/bin/sh -c" - lowerStr.includes('\\' + cmdLower + ' ') || // 7. Windows path with args: "C:\\bin\\sh.exe -c" - new RegExp(`\\b${escapedCmd}\\b`).test(lowerStr) - ) { - // 8. Word boundary match: catches embedded commands - throw new Error(`${contextPrefix}Dangerous command detected: "${cmd}" in "${str}"`) - } - } - - // 2. Check for null bytes (binary content or encoding attacks) - if (str.includes('\0')) { - throw new Error(`${contextPrefix}Null byte detected in string: "${str}"`) - } - } - - /** - * Recursively validates an object for dangerous content - * This function traverses the entire object tree to ensure no malicious content is hidden - * @param obj - The object to validate (can be string, array, object, or primitive) - * @param path - The current path in the object (for error reporting and debugging) - */ - function validateObject(obj: any, path: string = ''): void { - // Skip null/undefined values - if (obj === null || obj === undefined) return - - if (typeof obj === 'string') { - // Validate string content for dangerous commands and patterns - checkString(obj, path) - } else if (Array.isArray(obj)) { - // Recursively validate each array element - obj.forEach((item, index) => { - validateObject(item, `${path}[${index}]`) - }) - } else if (typeof obj === 'object') { - // Recursively validate each object property - for (const [key, value] of Object.entries(obj)) { - const currentPath = path ? `${path}.${key}` : key - // Validate only the property value - validateObject(value, currentPath) - } - } - } - - validateObject(serverParams, 'serverParams') +export function validateMCPServerSecurity(serverParams: Record): void { + // Whitelist of allowed commands - only these are permitted + const allowedCommands = ['npx', 'node'] if (serverParams.command) { const cmd = serverParams.command.toLowerCase() - if (cmd.includes('sh') || cmd.includes('cmd') || cmd.includes('powershell') || cmd.includes('eval')) { - throw new Error(`Command field contains dangerous interpreter: "${serverParams.command}"`) + const baseCmd = cmd + + if (!allowedCommands.includes(baseCmd)) { + throw new Error(`Only allowed: ${allowedCommands.join(', ')}`) } } @@ -555,18 +198,50 @@ export function validateMCPServerSecurity(serverParams: any): void { } } } +} - if (serverParams.cwd) { - checkString(serverParams.cwd, 'cwd') - const cwd = serverParams.cwd.toLowerCase() - if ( - cwd.startsWith('/bin') || - cwd.startsWith('/sbin') || - cwd.startsWith('/etc') || - cwd.includes('system32') || - cwd.includes('program files') - ) { - throw new Error(`Working directory points to sensitive system location: "${serverParams.cwd}"`) +export const validateArgsForLocalFileAccess = (args: string[]): void => { + const dangerousPatterns = [ + // Absolute paths + /^\/[^/]/, // Unix absolute paths starting with / + /^[a-zA-Z]:\\/, // Windows absolute paths like C:\ + + // Relative paths that could escape current directory + /\.\.\//, // Parent directory traversal with ../ + /\.\.\\/, // Parent directory traversal with ..\ + /^\.\./, // Starting with .. + + // Local file access patterns + /^\.\//, // Current directory with ./ + /^~\//, // Home directory with ~/ + /^file:\/\//, // File protocol + + // Common file extensions that shouldn't be accessed + /\.(exe|bat|cmd|sh|ps1|vbs|scr|com|pif|dll|sys)$/i, + + // File flags and options that could access local files + /^--?(?:file|input|output|config|load|save|import|export|read|write)=/i, + /^--?(?:file|input|output|config|load|save|import|export|read|write)$/i + ] + + for (const arg of args) { + if (typeof arg !== 'string') continue + + // Check for dangerous patterns + for (const pattern of dangerousPatterns) { + if (pattern.test(arg)) { + throw new Error(`Argument contains potential local file access: "${arg}"`) + } + } + + // Check for null bytes + if (arg.includes('\0')) { + throw new Error(`Argument contains null byte: "${arg}"`) + } + + // Check for very long paths that might be used for buffer overflow attacks + if (arg.length > 1000) { + throw new Error(`Argument is suspiciously long (${arg.length} characters): "${arg.substring(0, 100)}..."`) } } } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.jsx b/packages/ui/src/views/canvas/NodeInputHandler.jsx index c7ed270ec..04175e087 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.jsx +++ b/packages/ui/src/views/canvas/NodeInputHandler.jsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState, useContext } from 'react' import { useSelector, useDispatch } from 'react-redux' import { cloneDeep } from 'lodash' import showdown from 'showdown' +import parser from 'html-react-parser' // material-ui import { useTheme, styled } from '@mui/material/styles' @@ -966,7 +967,7 @@ const NodeInputHandler = ({ }} > - {inputParam.warning} + {parser(inputParam.warning)} )} {inputParam.type === 'credential' && (