Merge branch 'main' into bugfix/AgentflowV2-SessionID-Override

This commit is contained in:
Henry 2025-08-01 12:43:16 +01:00
commit 871f870823
4 changed files with 60 additions and 380 deletions

View File

@ -72,7 +72,11 @@ class Custom_MCP implements INode {
label: 'How to use',
value: howToUseCode
},
placeholder: mcpServerConfig
placeholder: mcpServerConfig,
warning:
process.env.CUSTOM_MCP_SECURITY_CHECK === 'true'
? 'In next release, only Remote MCP with url is supported. Read more <a href="https://docs.flowiseai.com/tutorials/tools-and-mcp#streamable-http-recommended" target="_blank">here</a>'
: undefined
},
{
label: 'Available Actions',

View File

@ -1,7 +1,7 @@
import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit, validateMCPServerSecurity } from '../core'
import { MCPToolkit, validateArgsForLocalFileAccess } from '../core'
class Supergateway_MCP implements INode {
label: string
@ -108,7 +108,7 @@ class Supergateway_MCP implements INode {
if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
try {
validateMCPServerSecurity(serverParams)
validateArgsForLocalFileAccess(processedArgs)
} catch (error) {
throw new Error(`Security validation failed: ${error.message}`)
}

View File

@ -176,375 +176,18 @@ function createSchemaModel(
/**
* TODO: To be removed and only allow Remote MCP for Cloud
* Validates MCP server configuration to prevent execution of dangerous commands
* Validates MCP server configuration to only allow whitelisted 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')
export function validateMCPServerSecurity(serverParams: Record<string, any>): void {
// Whitelist of allowed commands - only these are permitted
const allowedCommands = ['npx', 'node']
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}"`)
const baseCmd = cmd
if (!allowedCommands.includes(baseCmd)) {
throw new Error(`Only allowed: ${allowedCommands.join(', ')}`)
}
}
@ -555,18 +198,50 @@ export function validateMCPServerSecurity(serverParams: any): void {
}
}
}
}
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}"`)
export const validateArgsForLocalFileAccess = (args: string[]): void => {
const dangerousPatterns = [
// Absolute paths
/^\/[^/]/, // Unix absolute paths starting with /
/^[a-zA-Z]:\\/, // Windows absolute paths like C:\
// Relative paths that could escape current directory
/\.\.\//, // Parent directory traversal with ../
/\.\.\\/, // Parent directory traversal with ..\
/^\.\./, // Starting with ..
// Local file access patterns
/^\.\//, // Current directory with ./
/^~\//, // Home directory with ~/
/^file:\/\//, // File protocol
// Common file extensions that shouldn't be accessed
/\.(exe|bat|cmd|sh|ps1|vbs|scr|com|pif|dll|sys)$/i,
// File flags and options that could access local files
/^--?(?:file|input|output|config|load|save|import|export|read|write)=/i,
/^--?(?:file|input|output|config|load|save|import|export|read|write)$/i
]
for (const arg of args) {
if (typeof arg !== 'string') continue
// Check for dangerous patterns
for (const pattern of dangerousPatterns) {
if (pattern.test(arg)) {
throw new Error(`Argument contains potential local file access: "${arg}"`)
}
}
// Check for null bytes
if (arg.includes('\0')) {
throw new Error(`Argument contains null byte: "${arg}"`)
}
// Check for very long paths that might be used for buffer overflow attacks
if (arg.length > 1000) {
throw new Error(`Argument is suspiciously long (${arg.length} characters): "${arg.substring(0, 100)}..."`)
}
}
}

View File

@ -4,6 +4,7 @@ import { useEffect, useRef, useState, useContext } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { cloneDeep } from 'lodash'
import showdown from 'showdown'
import parser from 'html-react-parser'
// material-ui
import { useTheme, styled } from '@mui/material/styles'
@ -966,7 +967,7 @@ const NodeInputHandler = ({
}}
>
<IconAlertTriangle size={30} color='orange' />
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>{inputParam.warning}</span>
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>{parser(inputParam.warning)}</span>
</div>
)}
{inputParam.type === 'credential' && (