Compare commits

...

3 Commits

Author SHA1 Message Date
Henry 6669c8ffcc add validateMCPServerSecurity 2025-07-29 15:50:37 +01:00
Henry ca3818913e 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. 2025-07-28 23:42:48 +01:00
Henry 8e7a3a8b37 - Implemented a validation function to check for banned commands and dangerous patterns.
- Added checks for potential shell injection attempts in command and arguments.
- Security validation is conditionally enabled based on environment variable CUSTOM_MCP_SECURITY_CHECK.
2025-07-28 18:40:57 +01:00
3 changed files with 423 additions and 15 deletions

View File

@ -1,6 +1,6 @@
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'
@ -169,6 +169,10 @@ class Custom_MCP implements INode {
serverParams = JSON.parse(serverParamsString) serverParams = JSON.parse(serverParamsString)
} }
if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
validateMCPServerSecurity(serverParams)
}
// Compatible with stdio and SSE // Compatible with stdio and SSE
let toolkit: MCPToolkit let toolkit: MCPToolkit
if (serverParams?.command === undefined) { if (serverParams?.command === undefined) {

View File

@ -1,7 +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'
class Supergateway_MCP implements INode { class Supergateway_MCP implements INode {
label: string label: string
@ -90,21 +90,28 @@ 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
})
const serverParams = { const serverParams = {
command: 'node', command: 'node',
args: [ args: [packagePath, ...processedArgs]
packagePath, }
..._args
.trim() if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
.split(/\s+/) try {
.map((arg) => { validateMCPServerSecurity(serverParams)
// Remove surrounding double or single quotes if they exist } catch (error) {
if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { throw new Error(`Security validation failed: ${error.message}`)
return arg.slice(1, -1) }
}
return arg
})
]
} }
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}"`)
}
}
}