Merge branch 'main' into bugfix/AgentflowV2-SessionID-Override
This commit is contained in:
commit
871f870823
|
|
@ -72,7 +72,11 @@ class Custom_MCP implements INode {
|
||||||
label: 'How to use',
|
label: 'How to use',
|
||||||
value: howToUseCode
|
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',
|
label: 'Available Actions',
|
||||||
|
|
|
||||||
|
|
@ -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, validateMCPServerSecurity } from '../core'
|
import { MCPToolkit, validateArgsForLocalFileAccess } from '../core'
|
||||||
|
|
||||||
class Supergateway_MCP implements INode {
|
class Supergateway_MCP implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -108,7 +108,7 @@ class Supergateway_MCP implements INode {
|
||||||
|
|
||||||
if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
|
if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
|
||||||
try {
|
try {
|
||||||
validateMCPServerSecurity(serverParams)
|
validateArgsForLocalFileAccess(processedArgs)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Security validation failed: ${error.message}`)
|
throw new Error(`Security validation failed: ${error.message}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,375 +176,18 @@ function createSchemaModel(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: To be removed and only allow Remote MCP for Cloud
|
* 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 {
|
export function validateMCPServerSecurity(serverParams: Record<string, any>): void {
|
||||||
// Comprehensive list of dangerous commands that could compromise system security
|
// Whitelist of allowed commands - only these are permitted
|
||||||
const dangerousCommands = [
|
const allowedCommands = ['npx', 'node']
|
||||||
// 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) {
|
if (serverParams.command) {
|
||||||
const cmd = serverParams.command.toLowerCase()
|
const cmd = serverParams.command.toLowerCase()
|
||||||
if (cmd.includes('sh') || cmd.includes('cmd') || cmd.includes('powershell') || cmd.includes('eval')) {
|
const baseCmd = cmd
|
||||||
throw new Error(`Command field contains dangerous interpreter: "${serverParams.command}"`)
|
|
||||||
|
if (!allowedCommands.includes(baseCmd)) {
|
||||||
|
throw new Error(`Only allowed: ${allowedCommands.join(', ')}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -555,18 +198,50 @@ export function validateMCPServerSecurity(serverParams: any): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (serverParams.cwd) {
|
export const validateArgsForLocalFileAccess = (args: string[]): void => {
|
||||||
checkString(serverParams.cwd, 'cwd')
|
const dangerousPatterns = [
|
||||||
const cwd = serverParams.cwd.toLowerCase()
|
// Absolute paths
|
||||||
if (
|
/^\/[^/]/, // Unix absolute paths starting with /
|
||||||
cwd.startsWith('/bin') ||
|
/^[a-zA-Z]:\\/, // Windows absolute paths like C:\
|
||||||
cwd.startsWith('/sbin') ||
|
|
||||||
cwd.startsWith('/etc') ||
|
// Relative paths that could escape current directory
|
||||||
cwd.includes('system32') ||
|
/\.\.\//, // Parent directory traversal with ../
|
||||||
cwd.includes('program files')
|
/\.\.\\/, // Parent directory traversal with ..\
|
||||||
) {
|
/^\.\./, // Starting with ..
|
||||||
throw new Error(`Working directory points to sensitive system location: "${serverParams.cwd}"`)
|
|
||||||
|
// 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)}..."`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useEffect, useRef, useState, useContext } from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import showdown from 'showdown'
|
import showdown from 'showdown'
|
||||||
|
import parser from 'html-react-parser'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme, styled } from '@mui/material/styles'
|
import { useTheme, styled } from '@mui/material/styles'
|
||||||
|
|
@ -966,7 +967,7 @@ const NodeInputHandler = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconAlertTriangle size={30} color='orange' />
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{inputParam.type === 'credential' && (
|
{inputParam.type === 'credential' && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue