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.

This commit is contained in:
Henry 2025-07-28 23:42:48 +01:00
parent 8e7a3a8b37
commit ca3818913e
1 changed files with 154 additions and 13 deletions

View File

@ -3,6 +3,136 @@ import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from
import { getNodeModulesPackagePath } from '../../../../src/utils' import { getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core' 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 { class Supergateway_MCP implements INode {
label: string label: string
name: string name: string
@ -90,21 +220,32 @@ class Supergateway_MCP implements INode {
const _args = nodeData.inputs?.arguments as string const _args = nodeData.inputs?.arguments as string
const packagePath = getNodeModulesPackagePath('supergateway/dist/index.js') 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 = { const serverParams = {
command: 'node', command: 'node',
args: [ args: [packagePath, ...processedArgs]
packagePath, }
..._args
.trim() // Security validation: Check for dangerous commands
.split(/\s+/) // TODO: To be removed and only allow Remote MCP for Cloud
.map((arg) => { if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
// Remove surrounding double or single quotes if they exist validateCommand(serverParams.command)
if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) {
return arg.slice(1, -1)
}
return arg
})
]
} }
const toolkit = new MCPToolkit(serverParams, 'stdio') const toolkit = new MCPToolkit(serverParams, 'stdio')