add validateMCPServerSecurity

This commit is contained in:
Henry 2025-07-29 15:50:37 +01:00
parent ca3818913e
commit 6669c8ffcc
3 changed files with 406 additions and 263 deletions

View File

@ -1,105 +1,10 @@
import { Tool } from '@langchain/core/tools' import { Tool } from '@langchain/core/tools'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' 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 { getVars, prepareSandboxVars } from '../../../../src/utils'
import { DataSource } from 'typeorm' import { DataSource } from 'typeorm'
import hash from 'object-hash' 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 = `{ const mcpServerConfig = `{
"command": "npx", "command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"] "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
@ -264,33 +169,8 @@ class Custom_MCP implements INode {
serverParams = JSON.parse(serverParamsString) serverParams = JSON.parse(serverParamsString)
} }
// Security validation: Check for dangerous commands if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
// TODO: To be removed and only allow Remote MCP for Cloud validateMCPServerSecurity(serverParams)
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 // Compatible with stdio and SSE

View File

@ -1,137 +1,7 @@
import { Tool } from '@langchain/core/tools' import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getNodeModulesPackagePath } from '../../../../src/utils' import { getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core' import { MCPToolkit, validateMCPServerSecurity } 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
@ -231,21 +101,17 @@ class Supergateway_MCP implements INode {
return arg 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: [packagePath, ...processedArgs] 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') { 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') const toolkit = new MCPToolkit(serverParams, 'stdio')

View File

@ -173,3 +173,400 @@ function createSchemaModel(
return z.object(schemaProperties) 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}"`)
}
}
}