diff --git a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts index b10ece772..87e2af3da 100644 --- a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts +++ b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts @@ -5,6 +5,101 @@ 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"] @@ -169,6 +264,35 @@ 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.` + ) + } + } + } + } + // Compatible with stdio and SSE let toolkit: MCPToolkit if (serverParams?.command === undefined) {