Fix Flowise-552 Add Allow List Execution Path (#6658)

fix(flowise-552): add allow list execution path
This commit is contained in:
yau-wd
2026-07-24 08:43:26 +08:00
committed by GitHub
parent 441c4e9878
commit e816d26a7d
9 changed files with 55 additions and 91 deletions
+1
View File
@@ -199,6 +199,7 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse) 'stdio' can run arbitrary commands on your server, enable only if you trust all users
# CUSTOM_MCP_ALLOWED_ENV_VARS= #(comma-separated list of env var names a Custom MCP stdio config may set, e.g. BRAVE_API_KEY,GITHUB_TOKEN. Empty = none allowed)
# CUSTOM_MCP_ALLOWED_COMMANDS= #(comma-separated list of commands a Custom MCP stdio config may run. Empty = none allowed. Set only the commands you need from: node|npx|python|python3|docker)
# CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS= #WARNING: Only add scripts YOU control - misconfiguration allows arbitrary code execution. (comma-separated list of absolute file paths that Custom MCP stdio configs may execute. Empty = none allowed.Example: /usr/local/lib/server.js,/app/scripts/brave-search.js)
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
# OAUTH2_SECURITY_CHECK=true
# OAUTH2_ALLOWED_TOKEN_DOMAINS= #(comma-separated list of additional OAuth2 provider domains to allow, e.g. keycloak.mycompany.com,auth.custom-idp.com)
+2
View File
@@ -156,6 +156,7 @@ services:
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
- CUSTOM_MCP_ALLOWED_ENV_VARS=${CUSTOM_MCP_ALLOWED_ENV_VARS}
- CUSTOM_MCP_ALLOWED_COMMANDS=${CUSTOM_MCP_ALLOWED_COMMANDS}
- CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS=${CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS}
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
- HTTP_SECURITY_CHECK=${HTTP_SECURITY_CHECK}
- PATH_TRAVERSAL_SAFETY=${PATH_TRAVERSAL_SAFETY}
@@ -313,6 +314,7 @@ services:
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
- CUSTOM_MCP_ALLOWED_ENV_VARS=${CUSTOM_MCP_ALLOWED_ENV_VARS}
- CUSTOM_MCP_ALLOWED_COMMANDS=${CUSTOM_MCP_ALLOWED_COMMANDS}
- CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS=${CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS}
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
- HTTP_SECURITY_CHECK=${HTTP_SECURITY_CHECK}
- PATH_TRAVERSAL_SAFETY=${PATH_TRAVERSAL_SAFETY}
+1
View File
@@ -141,6 +141,7 @@ services:
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
- CUSTOM_MCP_ALLOWED_ENV_VARS=${CUSTOM_MCP_ALLOWED_ENV_VARS}
- CUSTOM_MCP_ALLOWED_COMMANDS=${CUSTOM_MCP_ALLOWED_COMMANDS}
- CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS=${CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS}
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
- HTTP_SECURITY_CHECK=${HTTP_SECURITY_CHECK}
- PATH_TRAVERSAL_SAFETY=${PATH_TRAVERSAL_SAFETY}
+1
View File
@@ -198,6 +198,7 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse) 'stdio' can run arbitrary commands on your server, enable only if you trust all users
# CUSTOM_MCP_ALLOWED_ENV_VARS= #(comma-separated list of env var names a Custom MCP stdio config may set, e.g. BRAVE_API_KEY,GITHUB_TOKEN. Empty = none allowed)
# CUSTOM_MCP_ALLOWED_COMMANDS= #(comma-separated list of commands a Custom MCP stdio config may run. Empty = none allowed. Set only the commands you need from: node|npx|python|python3|docker)
# CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS= #WARNING: Only add scripts YOU control - misconfiguration allows arbitrary code execution. (comma-separated list of absolute file paths that Custom MCP stdio configs may execute. Empty = none allowed.Example: /usr/local/lib/server.js,/app/scripts/brave-search.js)
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
# OAUTH2_SECURITY_CHECK=true
# OAUTH2_ALLOWED_TOKEN_DOMAINS= #(comma-separated list of additional OAuth2 provider domains to allow, e.g. keycloak.mycompany.com,auth.custom-idp.com)
+1
View File
@@ -140,6 +140,7 @@ services:
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
- CUSTOM_MCP_ALLOWED_ENV_VARS=${CUSTOM_MCP_ALLOWED_ENV_VARS}
- CUSTOM_MCP_ALLOWED_COMMANDS=${CUSTOM_MCP_ALLOWED_COMMANDS}
- CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS=${CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS}
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
- HTTP_SECURITY_CHECK=${HTTP_SECURITY_CHECK}
- PATH_TRAVERSAL_SAFETY=${PATH_TRAVERSAL_SAFETY}
@@ -363,53 +363,39 @@ describe('MCP Security Validations', () => {
})
describe('validateArgsForLocalFileAccess', () => {
it('should block absolute paths', () => {
expect(() => {
validateArgsForLocalFileAccess(['/etc/passwd'])
}).toThrow('Argument contains potential local file access')
const ORIGINAL_ENV = process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS
expect(() => {
validateArgsForLocalFileAccess(['C:\\Windows\\System32'])
}).toThrow('Argument contains potential local file access')
afterEach(() => {
if (ORIGINAL_ENV === undefined) {
delete process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS
} else {
process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS = ORIGINAL_ENV
}
})
it('should block double-slash absolute paths', () => {
it('should block when not configured', () => {
delete process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS
expect(() => {
validateArgsForLocalFileAccess(['//etc/passwd'])
}).toThrow('Argument contains potential local file access')
validateArgsForLocalFileAccess(['/usr/local/lib/server.js'])
}).toThrow('Configure CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS')
})
it('should block path traversal', () => {
expect(() => {
validateArgsForLocalFileAccess(['../../../etc/passwd'])
}).toThrow('Argument contains potential local file access')
it('should allow script in allow-list', () => {
process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS = '/usr/local/lib/server.js'
expect(() => {
validateArgsForLocalFileAccess(['..\\..\\Windows'])
}).toThrow('Argument contains potential local file access')
})
it('should block dangerous file extensions', () => {
expect(() => {
validateArgsForLocalFileAccess(['malware.exe'])
}).toThrow('Argument contains potential local file access')
expect(() => {
validateArgsForLocalFileAccess(['script.sh'])
}).toThrow('Argument contains potential local file access')
})
it('should block null bytes', () => {
expect(() => {
validateArgsForLocalFileAccess(['file\0.txt'])
}).toThrow('Argument contains null byte')
})
it('should allow safe arguments', () => {
expect(() => {
validateArgsForLocalFileAccess(['@modelcontextprotocol/server-github', 'safe-arg'])
validateArgsForLocalFileAccess(['/usr/local/lib/server.js'])
}).not.toThrow()
})
it('should block script not in allow-list', () => {
process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS = '/usr/local/lib/server.js'
expect(() => {
validateArgsForLocalFileAccess(['root/.flowise/malicious.js'])
}).toThrow('not in allowed list')
})
})
describe('validateEnvironmentVariables', () => {
@@ -462,11 +448,15 @@ describe('MCP Security Validations', () => {
describe('validateMCPServerConfig', () => {
const originalAllowedCommands = process.env.CUSTOM_MCP_ALLOWED_COMMANDS
const originalAllowedScriptPaths = process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS
beforeEach(() => {
// These tests assume the operator has permitted the common interpreters.
// The default (empty) allow-list is exercised separately below.
process.env.CUSTOM_MCP_ALLOWED_COMMANDS = 'node,npx,python,python3,docker'
// Allow common script paths for tests
process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS =
'@modelcontextprotocol/server-filesystem,workspace,safe-arg,mcp-server.js,server.js,/usr/local/lib/server.js'
})
afterEach(() => {
@@ -475,6 +465,12 @@ describe('MCP Security Validations', () => {
} else {
process.env.CUSTOM_MCP_ALLOWED_COMMANDS = originalAllowedCommands
}
if (originalAllowedScriptPaths === undefined) {
delete process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS
} else {
process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS = originalAllowedScriptPaths
}
})
it('should validate complete server configuration', () => {
@@ -498,30 +494,21 @@ describe('MCP Security Validations', () => {
it('should block dangerous command flags', () => {
expect(() => {
validateMCPServerConfig({
command: 'npx',
args: ['-c', 'malicious command']
command: 'node',
args: ['server.js', '-e', 'malicious code']
})
}).toThrow("Argument '-c' is not allowed for command 'npx'")
}).toThrow("Argument '-e' is not allowed for command 'node'")
})
it('should block command injection in args', () => {
expect(() => {
validateMCPServerConfig({
command: 'npx',
args: ['arg1; malicious']
command: 'node',
args: ['server.js', 'arg1; malicious']
})
}).toThrow('Argument contains potentially dangerous characters')
})
it('should block path traversal in args', () => {
expect(() => {
validateMCPServerConfig({
command: 'npx',
args: ['../../../etc/passwd']
})
}).toThrow('Argument contains potential local file access')
})
it('should block environment variables that are not on the allow-list', () => {
expect(() => {
validateMCPServerConfig({
+8 -40
View File
@@ -179,49 +179,17 @@ export async function MCPTool({
}
export const validateArgsForLocalFileAccess = (args: string[]): void => {
const dangerousPatterns = [
// Absolute paths
/^\//, // Unix absolute paths starting with /
/^[a-zA-Z]:\\/, // Windows absolute paths like C:\
const allowedScriptPaths = (process.env.CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
// Relative paths that could escape current directory
/\.\.\//, // Parent directory traversal with ../
/\.\.\\/, // Parent directory traversal with ..\
/^\.\./, // Starting with ..
const scriptArg = args[0]
// Local file access patterns
/^\.\//, // Current directory with ./
/^~\//, // Home directory with ~/
/^file:\/\//, // File protocol
if (allowedScriptPaths.length === 0)
throw new Error('Custom MCP script execution disabled. Configure CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS environment variable.')
// 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)}..."`)
}
}
if (!allowedScriptPaths.includes(scriptArg)) throw new Error('Custom MCP script path not in allowed list.')
}
export const validateCommandInjection = (args: string[]): void => {
+1
View File
@@ -198,6 +198,7 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse) 'stdio' can run arbitrary commands on your server, enable only if you trust all users
# CUSTOM_MCP_ALLOWED_ENV_VARS= #(comma-separated list of env var names a Custom MCP stdio config may set, e.g. BRAVE_API_KEY,GITHUB_TOKEN. Empty = none allowed)
# CUSTOM_MCP_ALLOWED_COMMANDS= #(comma-separated list of commands a Custom MCP stdio config may run. Empty = none allowed. Set only the commands you need from: node|npx|python|python3|docker)
# CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS= #WARNING: Only add scripts YOU control - misconfiguration allows arbitrary code execution. (comma-separated list of absolute file paths that Custom MCP stdio configs may execute. Empty = none allowed.Example: /usr/local/lib/server.js,/app/scripts/brave-search.js)
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
# OAUTH2_SECURITY_CHECK=true
# OAUTH2_ALLOWED_TOKEN_DOMAINS= #(comma-separated list of additional OAuth2 provider domains to allow, e.g. keycloak.mycompany.com,auth.custom-idp.com)
+2
View File
@@ -108,6 +108,8 @@ export abstract class BaseCommand extends Command {
CUSTOM_MCP_SECURITY_CHECK: Flags.string(),
CUSTOM_MCP_PROTOCOL: Flags.string(),
CUSTOM_MCP_ALLOWED_ENV_VARS: Flags.string(),
CUSTOM_MCP_ALLOWED_COMMANDS: Flags.string(),
CUSTOM_MCP_ALLOWED_ABSOLUTE_SCRIPT_PATHS: Flags.string(),
HTTP_DENY_LIST: Flags.string(),
HTTP_SECURITY_CHECK: Flags.string(),
PATH_TRAVERSAL_SAFETY: Flags.string(),