Skip to content

Commit 7c85b4f

Browse files
Merge branch 'main' into feat/agentflow-sdk-change-save-flowdata
2 parents 72eb58e + b4665df commit 7c85b4f

4 files changed

Lines changed: 514 additions & 4 deletions

File tree

packages/components/nodes/agents/CSVAgent/CSVAgent.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ class CSV_Agents implements INode {
144144
// For example using titanic.csv: {'PassengerId': 'int64', 'Survived': 'int64', 'Pclass': 'int64', 'Name': 'object', 'Sex': 'object', 'Age': 'float64', 'SibSp': 'int64', 'Parch': 'int64', 'Ticket': 'object', 'Fare': 'float64', 'Cabin': 'object', 'Embarked': 'object'}
145145
let dataframeColDict = ''
146146
let customReadCSVFunc = _customReadCSV ? _customReadCSV : 'read_csv(csv_data)'
147+
const csvReadValidation = validatePythonCodeForDataFrame(customReadCSVFunc)
148+
if (!csvReadValidation.valid) {
149+
throw new Error(
150+
`Custom read_csv code was rejected for security reasons (${
151+
csvReadValidation.reason ?? 'unsafe construct'
152+
}). Please use only safe pandas read_csv operations.`
153+
)
154+
}
147155
try {
148156
const code = `import pandas as pd
149157
import base64

packages/components/nodes/tools/MCP/core.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,90 @@ export const validateEnvironmentVariables = (env: Record<string, any>): void =>
259259
}
260260
}
261261

262+
/**
263+
* Validates that command arguments don't contain flags that enable arbitrary code execution
264+
* This prevents attacks where whitelisted commands are used with dangerous flags
265+
* (e.g., "npx -c malicious-command" or "python -c malicious-code")
266+
* @param command The command to validate
267+
* @param args The arguments to validate
268+
*/
269+
export const validateCommandFlags = (command: string, args: string[]): void => {
270+
// Define dangerous flags for each command that enable code execution
271+
const dangerousFlagsByCommand: Record<string, string[]> = {
272+
npx: [
273+
'-c', // Execute shell commands
274+
'--call', // Execute shell commands
275+
'--shell-auto-fallback', // Shell execution fallback
276+
'-y' // Auto-confirms installation prompts
277+
],
278+
node: [
279+
'-e', // Execute JavaScript code
280+
'--eval', // Execute JavaScript code
281+
'-p', // Evaluate and print JavaScript code
282+
'--print', // Evaluate and print JavaScript code
283+
'--inspect', // Enable remote debugging (security risk)
284+
'--inspect-brk', // Enable remote debugging with breakpoint (security risk)
285+
'--experimental-policy' // Could load malicious policies
286+
],
287+
python: [
288+
'-c', // Execute Python code
289+
'-m' // Run library modules (could run malicious modules)
290+
],
291+
python3: [
292+
'-c', // Execute Python code
293+
'-m' // Run library modules (could run malicious modules)
294+
],
295+
docker: [
296+
'run', // Run containers (too powerful)
297+
'exec', // Execute in containers
298+
'-v', // Mount host filesystems
299+
'--volume', // Mount host filesystems
300+
'--privileged', // Privileged mode
301+
'--cap-add', // Add capabilities
302+
'--security-opt', // Modify security options
303+
'--network', // Host network access (catches --network=host and --network host)
304+
'--pid', // Host PID namespace (catches --pid=host and --pid host)
305+
'--ipc' // Host IPC namespace (catches --ipc=host and --ipc host)
306+
]
307+
}
308+
309+
const dangerousFlags = dangerousFlagsByCommand[command] || []
310+
311+
// Collect single-char dangerous flags (e.g. '-c' -> 'c') for combined flag detection
312+
const dangerousShortChars = new Set(dangerousFlags.filter((f) => /^-[a-zA-Z]$/.test(f)).map((f) => f[1].toLowerCase()))
313+
314+
for (const arg of args) {
315+
if (typeof arg !== 'string') continue
316+
317+
const normalizedArg = arg.toLowerCase().trim()
318+
319+
// Check for dangerous flags in various forms (exact, =value, space-separated value)
320+
for (const flag of dangerousFlags) {
321+
const lowerCaseFlag = flag.toLowerCase()
322+
if (normalizedArg === lowerCaseFlag) {
323+
throw new Error(`Argument '${arg}' is not allowed for command '${command}'.`)
324+
}
325+
if (normalizedArg.startsWith(lowerCaseFlag + '=')) {
326+
throw new Error(`Argument '${arg}' contains flag '${flag}' that is not allowed for command '${command}'.`)
327+
}
328+
if (flag.startsWith('-') && normalizedArg.startsWith(lowerCaseFlag + ' ')) {
329+
throw new Error(`Argument '${arg}' contains flag '${flag}' that is not allowed for command '${command}'.`)
330+
}
331+
}
332+
333+
// Check for combined short flags (e.g. "-yc" = "-y" + "-c")
334+
// A combined flag starts with a single '-', is not a long flag '--', and has multiple characters after '-'
335+
if (/^-[a-zA-Z]{2,}/.test(normalizedArg)) {
336+
const flagChars = normalizedArg.slice(1) // strip leading '-'
337+
for (const ch of flagChars) {
338+
if (dangerousShortChars.has(ch)) {
339+
throw new Error(`Argument '${arg}' contains dangerous flag '-${ch}' for command '${command}'.`)
340+
}
341+
}
342+
}
343+
}
344+
}
345+
262346
export const validateMCPServerConfig = (serverParams: any): void => {
263347
// Validate the entire server configuration
264348
if (!serverParams || typeof serverParams !== 'object') {
@@ -276,6 +360,11 @@ export const validateMCPServerConfig = (serverParams: any): void => {
276360
if (serverParams.args && Array.isArray(serverParams.args)) {
277361
validateArgsForLocalFileAccess(serverParams.args)
278362
validateCommandInjection(serverParams.args)
363+
364+
// Validate command-specific dangerous flags
365+
if (serverParams.command) {
366+
validateCommandFlags(serverParams.command, serverParams.args)
367+
}
279368
}
280369

281370
// Validate environment variables

packages/components/src/httpSecurity.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,19 @@ const DEFAULT_DENY_LIST = [
2727
]
2828

2929
/**
30-
* Gets the HTTP deny list from environment variable or returns default
31-
* @returns Array of denied IP addresses/CIDR ranges
30+
* Gets the HTTP deny list, always including default protections plus any custom entries
31+
* @returns Array of denied IP addresses/CIDR ranges (always includes DEFAULT_DENY_LIST)
3232
*/
3333
function getHttpDenyList(): string[] {
3434
const httpDenyListString = process.env.HTTP_DENY_LIST
35-
return httpDenyListString ? httpDenyListString.split(',').map((s) => s.trim()) : DEFAULT_DENY_LIST
35+
if (httpDenyListString) {
36+
const customList = httpDenyListString
37+
.split(',')
38+
.map((s) => s.trim())
39+
.filter(Boolean)
40+
return [...new Set([...DEFAULT_DENY_LIST, ...customList])]
41+
}
42+
return DEFAULT_DENY_LIST
3643
}
3744

3845
/**
@@ -97,7 +104,7 @@ export async function secureAxiosRequest(config: AxiosRequestConfig, maxRedirect
97104
}
98105

99106
let redirects = 0
100-
let currentConfig = { ...config, maxRedirects: 0 } // Disable automatic redirects
107+
let currentConfig = { ...config, maxRedirects: 0, validateStatus: () => true } // Disable automatic redirects, accept all status codes
101108

102109
while (redirects <= maxRedirects) {
103110
const target = await resolveAndValidate(currentUrl)

0 commit comments

Comments
 (0)