From ca3818913ea10f3502b21e705e04242a29c4176f Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 28 Jul 2025 23:42:48 +0100 Subject: [PATCH] Enhance security by implementing command and argument validation in SupergatewayMCP. Added checks for banned commands, dangerous patterns, and potential shell injection attempts. Security validation is conditionally enabled based on the CUSTOM_MCP_SECURITY_CHECK environment variable. --- .../tools/MCP/Supergateway/SupergatewayMCP.ts | 167 ++++++++++++++++-- 1 file changed, 154 insertions(+), 13 deletions(-) diff --git a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts index b8e1d6285..d7be92235 100644 --- a/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts +++ b/packages/components/nodes/tools/MCP/Supergateway/SupergatewayMCP.ts @@ -3,6 +3,136 @@ import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from 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.`) + } + } + } +} + class Supergateway_MCP implements INode { label: string name: string @@ -90,21 +220,32 @@ class Supergateway_MCP implements INode { const _args = nodeData.inputs?.arguments as string const packagePath = getNodeModulesPackagePath('supergateway/dist/index.js') + const processedArgs = _args + .trim() + .split(/\s+/) + .map((arg) => { + // Remove surrounding double or single quotes if they exist + if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { + return arg.slice(1, -1) + } + 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, - ..._args - .trim() - .split(/\s+/) - .map((arg) => { - // Remove surrounding double or single quotes if they exist - if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { - return arg.slice(1, -1) - } - return arg - }) - ] + 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) } const toolkit = new MCPToolkit(serverParams, 'stdio')