diff --git a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts index 87e2af3da..ff39a21fa 100644 --- a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts +++ b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts @@ -1,105 +1,10 @@ import { Tool } from '@langchain/core/tools' import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' -import { MCPToolkit } from '../core' +import { MCPToolkit, validateMCPServerSecurity } from '../core' import { getVars, prepareSandboxVars } from '../../../../src/utils' import { DataSource } from 'typeorm' import hash from 'object-hash' -// List of dangerous commands that are banned for security reasons -const BANNED_COMMANDS = [ - 'sh', - 'bash', - 'zsh', - 'fish', - 'csh', - 'tcsh', - 'ksh', - 'dash', - '/bin/sh', - '/bin/bash', - '/bin/zsh', - '/bin/fish', - '/bin/csh', - '/bin/tcsh', - '/bin/ksh', - '/bin/dash', - '/usr/bin/sh', - '/usr/bin/bash', - '/usr/bin/zsh', - '/usr/bin/fish', - '/usr/bin/csh', - '/usr/bin/tcsh', - '/usr/bin/ksh', - '/usr/bin/dash', - 'cmd', - 'cmd.exe', - 'powershell', - 'powershell.exe', - 'pwsh', - 'pwsh.exe', - 'perl', - 'ruby', - 'php', - 'eval', - 'exec', - 'system' -] - -// Additional dangerous command patterns to check -const DANGEROUS_PATTERNS = [ - /^\/bin\//, - /^\/usr\/bin\//, - /^\/usr\/local\/bin\//, - /^\/sbin\//, - /^\/usr\/sbin\//, - /\.exe$/i, - /\.bat$/i, - /\.cmd$/i, - /\.ps1$/i, - /\.sh$/i -] - -function validateCommand(command: string): void { - if (!command || typeof command !== 'string') { - return - } - - const normalizedCommand = command.toLowerCase().trim() - - // Check against banned commands list - for (const bannedCmd of BANNED_COMMANDS) { - if ( - normalizedCommand === bannedCmd.toLowerCase() || - normalizedCommand.endsWith(`/${bannedCmd.toLowerCase()}`) || - normalizedCommand.endsWith(`\\${bannedCmd.toLowerCase()}`) - ) { - throw new Error( - `Security Error: Command "${command}" is banned for security reasons. Shell access and executable commands are not allowed.` - ) - } - } - - // Check against dangerous patterns - for (const pattern of DANGEROUS_PATTERNS) { - if (pattern.test(normalizedCommand)) { - throw new Error(`Security Error: Command "${command}" matches a dangerous pattern and is not allowed for security reasons.`) - } - } - - // Additional checks for potential shell injection attempts - if ( - normalizedCommand.includes('&&') || - normalizedCommand.includes('||') || - normalizedCommand.includes(';') || - normalizedCommand.includes('|') || - normalizedCommand.includes('`') || - normalizedCommand.includes('$(') || - normalizedCommand.includes('${') - ) { - throw new Error(`Security Error: Command "${command}" contains potentially dangerous shell operators and is not allowed.`) - } -} - const mcpServerConfig = `{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"] @@ -264,33 +169,8 @@ class Custom_MCP implements INode { serverParams = JSON.parse(serverParamsString) } - // Security validation: Check for dangerous commands - // TODO: To be removed and only allow Remote MCP for Cloud - if (serverParams?.command && process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { - validateCommand(serverParams.command) - } - - // Also validate any commands in args that might be suspicious - // TODO: To be removed and only allow Remote MCP for Cloud - if (serverParams?.args && Array.isArray(serverParams.args) && process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { - for (const arg of serverParams.args) { - if (typeof arg === 'string') { - // Check if any argument looks like it's trying to execute a shell command - const suspiciousArg = arg.toLowerCase().trim() - if ( - suspiciousArg.startsWith('/bin/') || - suspiciousArg.startsWith('/usr/bin/') || - suspiciousArg.includes('sh') || - suspiciousArg.includes('bash') || - suspiciousArg.includes('cmd') || - suspiciousArg.includes('powershell') - ) { - throw new Error( - `Security Error: Argument "${arg}" contains potentially dangerous command references and is not allowed.` - ) - } - } - } + if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { + validateMCPServerSecurity(serverParams) } // Compatible with stdio and SSE diff --git a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts index d7be92235..347a35773 100644 --- a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts +++ b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts @@ -1,137 +1,7 @@ import { Tool } from '@langchain/core/tools' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' import { getNodeModulesPackagePath } from '../../../../src/utils' -import { MCPToolkit } from '../core' - -// List of dangerous commands that are banned for security reasons -const BANNED_COMMANDS = [ - 'sh', - 'bash', - 'zsh', - 'fish', - 'csh', - 'tcsh', - 'ksh', - 'dash', - '/bin/sh', - '/bin/bash', - '/bin/zsh', - '/bin/fish', - '/bin/csh', - '/bin/tcsh', - '/bin/ksh', - '/bin/dash', - '/usr/bin/sh', - '/usr/bin/bash', - '/usr/bin/zsh', - '/usr/bin/fish', - '/usr/bin/csh', - '/usr/bin/tcsh', - '/usr/bin/ksh', - '/usr/bin/dash', - 'cmd', - 'cmd.exe', - 'powershell', - 'powershell.exe', - 'pwsh', - 'pwsh.exe', - 'perl', - 'ruby', - 'php', - 'eval', - 'exec', - 'system' -] - -// Additional dangerous command patterns to check -const DANGEROUS_PATTERNS = [ - /^\/bin\//, - /^\/usr\/bin\//, - /^\/usr\/local\/bin\//, - /^\/sbin\//, - /^\/usr\/sbin\//, - /\.exe$/i, - /\.bat$/i, - /\.cmd$/i, - /\.ps1$/i, - /\.sh$/i -] - -function validateCommand(command: string): void { - if (!command || typeof command !== 'string') { - return - } - - const normalizedCommand = command.toLowerCase().trim() - - // Check against banned commands list - for (const bannedCmd of BANNED_COMMANDS) { - if ( - normalizedCommand === bannedCmd.toLowerCase() || - normalizedCommand.endsWith(`/${bannedCmd.toLowerCase()}`) || - normalizedCommand.endsWith(`\\${bannedCmd.toLowerCase()}`) - ) { - throw new Error( - `Security Error: Command "${command}" is banned for security reasons. Shell access and executable commands are not allowed.` - ) - } - } - - // Check against dangerous patterns - for (const pattern of DANGEROUS_PATTERNS) { - if (pattern.test(normalizedCommand)) { - throw new Error(`Security Error: Command "${command}" matches a dangerous pattern and is not allowed for security reasons.`) - } - } - - // Additional checks for potential shell injection attempts - if ( - normalizedCommand.includes('&&') || - normalizedCommand.includes('||') || - normalizedCommand.includes(';') || - normalizedCommand.includes('|') || - normalizedCommand.includes('`') || - normalizedCommand.includes('$(') || - normalizedCommand.includes('${') - ) { - throw new Error(`Security Error: Command "${command}" contains potentially dangerous shell operators and is not allowed.`) - } -} - -function validateArguments(args: string[]): void { - if (!args || !Array.isArray(args)) { - return - } - - for (const arg of args) { - if (typeof arg === 'string') { - // Check if any argument looks like it's trying to execute a shell command - const suspiciousArg = arg.toLowerCase().trim() - if ( - suspiciousArg.startsWith('/bin/') || - suspiciousArg.startsWith('/usr/bin/') || - suspiciousArg.includes('sh') || - suspiciousArg.includes('bash') || - suspiciousArg.includes('cmd') || - suspiciousArg.includes('powershell') - ) { - throw new Error(`Security Error: Argument "${arg}" contains potentially dangerous command references and is not allowed.`) - } - - // Check for shell injection attempts in arguments - if ( - suspiciousArg.includes('&&') || - suspiciousArg.includes('||') || - suspiciousArg.includes(';') || - suspiciousArg.includes('`') || - suspiciousArg.includes('$(') || - suspiciousArg.includes('${') - ) { - throw new Error(`Security Error: Argument "${arg}" contains potentially dangerous shell operators and is not allowed.`) - } - } - } -} +import { MCPToolkit, validateMCPServerSecurity } from '../core' class Supergateway_MCP implements INode { label: string @@ -231,21 +101,17 @@ class Supergateway_MCP implements INode { return arg }) - // Security validation: Check for dangerous arguments - // TODO: To be removed and only allow Remote MCP for Cloud - if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { - validateArguments(processedArgs) - } - const serverParams = { command: 'node', args: [packagePath, ...processedArgs] } - // Security validation: Check for dangerous commands - // TODO: To be removed and only allow Remote MCP for Cloud if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') { - validateCommand(serverParams.command) + try { + validateMCPServerSecurity(serverParams) + } catch (error) { + throw new Error(`Security validation failed: ${error.message}`) + } } const toolkit = new MCPToolkit(serverParams, 'stdio') diff --git a/packages/components/nodes/tools/MCP/core.ts b/packages/components/nodes/tools/MCP/core.ts index 6c6f49f24..be9e65f56 100644 --- a/packages/components/nodes/tools/MCP/core.ts +++ b/packages/components/nodes/tools/MCP/core.ts @@ -173,3 +173,400 @@ function createSchemaModel( return z.object(schemaProperties) } + +/** + * TODO: To be removed and only allow Remote MCP for Cloud + * Validates MCP server configuration to prevent execution of dangerous 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') + + 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}"`) + } + } + + if (serverParams.env) { + for (const [key, value] of Object.entries(serverParams.env)) { + if (typeof value === 'string' && (value.includes('$(') || value.includes('`'))) { + throw new Error(`Environment variable "${key}" contains command substitution: "${value}"`) + } + } + } + + 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}"`) + } + } +}