Refactor/Update code execution sandbox implementation across components (#4904)

refactor: Update code execution sandbox implementation across components

- Replaced NodeVM usage with a new createCodeExecutionSandbox function for improved sandbox management.
- Enhanced JavaScript code execution with executeJavaScriptCode function, allowing for better handling of libraries and output streaming.
- Updated multiple components to utilize the new sandboxing approach, ensuring consistent execution environment.
- Added validation for UUIDs and URLs in various tools to enhance input safety.
- Refactored input handling in CustomFunction and IfElseFunction to streamline variable management.
This commit is contained in:
Henry Heng 2025-07-21 00:09:01 +01:00 committed by GitHub
parent 9a06a85a8d
commit dca91b979b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 550 additions and 488 deletions

View File

@ -8,8 +8,7 @@ import {
INodeParams,
IServerSideEventStreamer
} from '../../../src/Interface'
import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../../src/utils'
import { NodeVM } from '@flowiseai/nodevm'
import { getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { updateFlowState } from '../utils'
interface ICustomFunctionInputVariables {
@ -19,9 +18,9 @@ interface ICustomFunctionInputVariables {
const exampleFunc = `/*
* You can use any libraries imported in Flowise
* You can use properties specified in Input Schema as variables. Ex: Property = userid, Variable = $userid
* You can use properties specified in Input Variables with the prefix $. For example: $foo
* You can get default flow config: $flow.sessionId, $flow.chatId, $flow.chatflowId, $flow.input, $flow.state
* You can get custom variables: $vars.<variable-name>
* You can get global variables: $vars.<variable-name>
* Must return a string value at the end of function
*/
@ -161,55 +160,36 @@ class CustomFunction_Agentflow implements INode {
state: newState
}
let sandbox: any = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
// Create additional sandbox variables for custom function inputs
const additionalSandbox: ICommonObject = {}
for (const item of functionInputVariables) {
const variableName = item.variableName
const variableValue = item.variableValue
sandbox[`$${variableName}`] = variableValue
additionalSandbox[`$${variableName}`] = variableValue
}
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
// Setup streaming function if needed
const streamOutput = isStreamable
? (output: string) => {
const sseStreamer: IServerSideEventStreamer = options.sseStreamer
sseStreamer.streamTokenEvent(chatId, output)
}
: undefined
const vm = new NodeVM(nodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
libraries: ['axios'],
streamOutput,
timeout: 10000
})
let finalOutput = response
if (typeof response === 'object') {
finalOutput = JSON.stringify(response, null, 2)
}
if (isStreamable) {
const sseStreamer: IServerSideEventStreamer = options.sseStreamer
sseStreamer.streamTokenEvent(chatId, finalOutput)
}
// Process template variables in state
if (newState && Object.keys(newState).length > 0) {
for (const key in newState) {

View File

@ -1,7 +1,6 @@
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
import { getVars, handleEscapeCharacters, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
class CustomDocumentLoader_DocumentLoaders implements INode {
label: string
@ -106,44 +105,23 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
}
}
let sandbox: any = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {}
// Add input variables to sandbox
if (Object.keys(inputVars).length) {
for (const item in inputVars) {
sandbox[`$${item}`] = inputVars[item]
additionalSandbox[`$${item}`] = inputVars[item]
}
}
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
libraries: ['axios'],
timeout: 10000
})
if (output === 'document' && Array.isArray(response)) {
if (response.length === 0) return response

View File

@ -1,7 +1,6 @@
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, transformBracesWithColon } from '../../../src/utils'
import { getBaseClasses, transformBracesWithColon, getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate } from '@langchain/core/prompts'
import { getVM } from '../../sequentialagents/commonUtils'
import { DataSource } from 'typeorm'
const defaultFunc = `const { AIMessage, HumanMessage, ToolMessage } = require('@langchain/core/messages');
@ -120,13 +119,29 @@ class ChatPromptTemplate_Prompts implements INode {
) {
const appDataSource = options.appDataSource as DataSource
const databaseEntities = options.databaseEntities as IDatabaseEntity
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, {})
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
const flow = {
chatflowId: options.chatflowid,
sessionId: options.sessionId,
chatId: options.chatId
}
const sandbox = createCodeExecutionSandbox('', variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${messageHistoryCode}}()`, __dirname)
if (!Array.isArray(response)) throw new Error('Returned message history must be an array')
const response = await executeJavaScriptCode(messageHistoryCode, sandbox, {
libraries: ['axios', '@langchain/core'],
timeout: 10000
})
const parsedResponse = JSON.parse(response)
if (!Array.isArray(parsedResponse)) {
throw new Error('Returned message history must be an array')
}
prompt = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(systemMessagePrompt),
...response,
...parsedResponse,
HumanMessagePromptTemplate.fromTemplate(humanMessagePrompt)
])
} catch (e) {

View File

@ -36,11 +36,12 @@ import {
handleEscapeCharacters,
prepareSandboxVars,
removeInvalidImageMarkdown,
transformBracesWithColon
transformBracesWithColon,
executeJavaScriptCode,
createCodeExecutionSandbox
} from '../../../src/utils'
import {
customGet,
getVM,
processImageMessage,
transformObjectPropertyToFunction,
filterConversationHistory,
@ -936,9 +937,13 @@ const getReturnOutput = async (nodeData: INodeData, input: string, options: ICom
throw new Error(e)
}
} else if (selectedTab === 'updateStateMemoryCode' && updateStateMemoryCode) {
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, flow)
const sandbox = createCodeExecutionSandbox(input, variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${updateStateMemoryCode}}()`, __dirname)
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
timeout: 10000
})
if (typeof response !== 'object') throw new Error('Return output must be an object')
return response
} catch (e) {

View File

@ -10,8 +10,8 @@ import {
ISeqAgentNode,
ISeqAgentsState
} from '../../../src/Interface'
import { checkCondition, customGet, getVM } from '../commonUtils'
import { getVars, prepareSandboxVars } from '../../../src/utils'
import { checkCondition, customGet } from '../commonUtils'
import { getVars, prepareSandboxVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
const howToUseCode = `
1. Must return a string value at the end of function. For example:
@ -279,9 +279,13 @@ const runCondition = async (nodeData: INodeData, input: string, options: ICommon
}
if (selectedTab === 'conditionFunction' && conditionFunction) {
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, flow)
const sandbox = createCodeExecutionSandbox(input, variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${conditionFunction}}()`, __dirname)
const response = await executeJavaScriptCode(conditionFunction, sandbox, {
timeout: 10000
})
if (typeof response !== 'string') throw new Error('Condition function must return a string')
return response
} catch (e) {

View File

@ -16,12 +16,19 @@ import {
ISeqAgentNode,
ISeqAgentsState
} from '../../../src/Interface'
import { getInputVariables, getVars, handleEscapeCharacters, prepareSandboxVars, transformBracesWithColon } from '../../../src/utils'
import {
getInputVariables,
getVars,
handleEscapeCharacters,
prepareSandboxVars,
transformBracesWithColon,
executeJavaScriptCode,
createCodeExecutionSandbox
} from '../../../src/utils'
import {
checkCondition,
convertStructuredSchemaToZod,
customGet,
getVM,
transformObjectPropertyToFunction,
filterConversationHistory,
restructureMessages
@ -539,9 +546,13 @@ const runCondition = async (
}
if (selectedTab === 'conditionFunction' && conditionFunction) {
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, flow)
const sandbox = createCodeExecutionSandbox(input, variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${conditionFunction}}()`, __dirname)
const response = await executeJavaScriptCode(conditionFunction, sandbox, {
timeout: 10000
})
if (typeof response !== 'string') throw new Error('Condition function must return a string')
return response
} catch (e) {

View File

@ -1,6 +1,5 @@
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
import { getVars, handleEscapeCharacters, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams, ISeqAgentNode, ISeqAgentsState } from '../../../src/Interface'
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages'
import { customGet } from '../commonUtils'
@ -154,44 +153,22 @@ class CustomFunction_SeqAgents implements INode {
}
}
let sandbox: any = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {}
// Add input variables to sandbox
if (Object.keys(inputVars).length) {
for (const item in inputVars) {
sandbox[`$${item}`] = inputVars[item]
additionalSandbox[`$${item}`] = inputVars[item]
}
}
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
timeout: 10000
})
if (returnValueAs === 'stateObj') {
if (typeof response !== 'object') {

View File

@ -1,13 +1,6 @@
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import {
availableDependencies,
defaultAllowBuiltInDep,
getCredentialData,
getCredentialParam,
getVars,
prepareSandboxVars
} from '../../../src/utils'
import { getCredentialData, getCredentialParam, getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { isValidUUID, isValidURL } from '../../../src/validator'
import {
ICommonObject,
IDatabaseEntity,
@ -177,6 +170,16 @@ class ExecuteFlow_SeqAgents implements INode {
const baseURL = (nodeData.inputs?.baseURL as string) || (options.baseURL as string)
const returnValueAs = nodeData.inputs?.returnValueAs as string
// Validate selectedFlowId is a valid UUID
if (!selectedFlowId || !isValidUUID(selectedFlowId)) {
throw new Error('Invalid flow ID: must be a valid UUID')
}
// Validate baseURL is a valid URL
if (!baseURL || !isValidURL(baseURL)) {
throw new Error('Invalid base URL: must be a valid URL')
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData)
@ -233,18 +236,13 @@ class ExecuteFlow_SeqAgents implements INode {
body: JSON.stringify(body)
}
let sandbox: ICommonObject = {
$input: flowInput,
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: callOptions,
$callBody: body,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
$callBody: body
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
const sandbox = createCodeExecutionSandbox(flowInput, variables, flow, additionalSandbox)
const code = `
const fetch = require('node-fetch');
@ -264,27 +262,11 @@ class ExecuteFlow_SeqAgents implements INode {
}
`
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
let response = await vm.run(`module.exports = async function() {${code}}()`, __dirname)
let response = await executeJavaScriptCode(code, sandbox, {
useSandbox: false,
timeout: 10000
})
if (typeof response === 'object') {
response = JSON.stringify(response)

View File

@ -24,12 +24,13 @@ import {
getVars,
handleEscapeCharacters,
prepareSandboxVars,
transformBracesWithColon
transformBracesWithColon,
executeJavaScriptCode,
createCodeExecutionSandbox
} from '../../../src/utils'
import {
convertStructuredSchemaToZod,
customGet,
getVM,
processImageMessage,
transformObjectPropertyToFunction,
filterConversationHistory,
@ -708,9 +709,13 @@ const getReturnOutput = async (nodeData: INodeData, input: string, options: ICom
throw new Error(e)
}
} else if (selectedTab === 'updateStateMemoryCode' && updateStateMemoryCode) {
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, flow)
const sandbox = createCodeExecutionSandbox(input, variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${updateStateMemoryCode}}()`, __dirname)
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
timeout: 10000
})
if (typeof response !== 'object') throw new Error('Return output must be an object')
return response
} catch (e) {

View File

@ -1,13 +1,16 @@
import { START } from '@langchain/langgraph'
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams, ISeqAgentNode } from '../../../src/Interface'
import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../../src/utils'
import { getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
const defaultFunc = `{
aggregate: {
value: (x, y) => x.concat(y), // here we append the new message to the existing messages
default: () => []
},
replacedValue: {
value: (x, y) => y ?? x,
default: () => null
}
}`
@ -198,37 +201,13 @@ class State_SeqAgents implements INode {
input
}
let sandbox: any = {
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
const sandbox = createCodeExecutionSandbox('', variables, flow)
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {return ${stateMemoryCode}}()`, __dirname)
const response = await executeJavaScriptCode(`return ${stateMemoryCode}`, sandbox, {
timeout: 10000
})
if (typeof response !== 'object') throw new Error('State must be an object')
const returnOutput: ISeqAgentNode = {
id: nodeData.id,

View File

@ -15,8 +15,8 @@ import { RunnableConfig } from '@langchain/core/runnables'
import { ARTIFACTS_PREFIX, SOURCE_DOCUMENTS_PREFIX, TOOL_ARGS_PREFIX } from '../../../src/agents'
import { Document } from '@langchain/core/documents'
import { DataSource } from 'typeorm'
import { MessagesState, RunnableCallable, customGet, getVM } from '../commonUtils'
import { getVars, prepareSandboxVars } from '../../../src/utils'
import { MessagesState, RunnableCallable, customGet } from '../commonUtils'
import { getVars, prepareSandboxVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ChatPromptTemplate } from '@langchain/core/prompts'
const defaultApprovalPrompt = `You are about to execute tool: {tools}. Ask if user want to proceed`
@ -572,9 +572,13 @@ const getReturnOutput = async (
throw new Error(e)
}
} else if (selectedTab === 'updateStateMemoryCode' && updateStateMemoryCode) {
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, flow)
const sandbox = createCodeExecutionSandbox(input, variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${updateStateMemoryCode}}()`, __dirname)
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
timeout: 10000
})
if (typeof response !== 'object') throw new Error('Return output must be an object')
return response
} catch (e) {

View File

@ -1,7 +1,6 @@
import { get } from 'lodash'
import { z } from 'zod'
import { DataSource } from 'typeorm'
import { NodeVM } from '@flowiseai/nodevm'
import { StructuredTool } from '@langchain/core/tools'
import { ChatMistralAI } from '@langchain/mistralai'
import { ChatAnthropic } from '@langchain/anthropic'
@ -17,7 +16,7 @@ import {
IVisionChatModal,
ConversationHistorySelection
} from '../../src/Interface'
import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../src/utils'
import { getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../src/utils'
import { ChatPromptTemplate, BaseMessagePromptTemplateLike } from '@langchain/core/prompts'
export const checkCondition = (input: string | number | undefined, condition: string, value: string | number = ''): boolean => {
@ -150,46 +149,6 @@ export const processImageMessage = async (llm: BaseChatModel, nodeData: INodeDat
return multiModalMessageContent
}
export const getVM = async (
appDataSource: DataSource,
databaseEntities: IDatabaseEntity,
nodeData: INodeData,
options: ICommonObject,
flow: ICommonObject
) => {
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
let sandbox: any = {
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
return new NodeVM(nodeVMOptions)
}
export const customGet = (obj: any, path: string) => {
if (path.includes('[-1]')) {
const parts = path.split('.')
@ -426,9 +385,21 @@ export const checkMessageHistory = async (
if (messageHistory) {
const appDataSource = options.appDataSource as DataSource
const databaseEntities = options.databaseEntities as IDatabaseEntity
const vm = await getVM(appDataSource, databaseEntities, nodeData, options, {})
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
const flow = {
chatflowId: options.chatflowid,
sessionId: options.sessionId,
chatId: options.chatId
}
const sandbox = createCodeExecutionSandbox('', variables, flow)
try {
const response = await vm.run(`module.exports = async function() {${messageHistory}}()`, __dirname)
const response = await executeJavaScriptCode(messageHistory, sandbox, {
timeout: 10000
})
if (!Array.isArray(response)) throw new Error('Returned message history must be an array')
if (sysPrompt) {
// insert at index 1

View File

@ -1,11 +1,11 @@
import { DataSource } from 'typeorm'
import { z } from 'zod'
import { NodeVM } from '@flowiseai/nodevm'
import { RunnableConfig } from '@langchain/core/runnables'
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
import { StructuredTool } from '@langchain/core/tools'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { availableDependencies, defaultAllowBuiltInDep, getCredentialData, getCredentialParam } from '../../../src/utils'
import { getCredentialData, getCredentialParam, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { isValidUUID, isValidURL } from '../../../src/validator'
import { v4 as uuidv4 } from 'uuid'
class AgentAsTool_Tools implements INode {
@ -160,6 +160,16 @@ class AgentAsTool_Tools implements INode {
const baseURL = (nodeData.inputs?.baseURL as string) || (options.baseURL as string)
// Validate agentflowid is a valid UUID
if (!selectedAgentflowId || !isValidUUID(selectedAgentflowId)) {
throw new Error('Invalid agentflow ID: must be a valid UUID')
}
// Validate baseURL is a valid URL
if (!baseURL || !isValidURL(baseURL)) {
throw new Error('Invalid base URL: must be a valid URL')
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const agentflowApiKey = getCredentialParam('agentflowApiKey', credentialData, nodeData)
@ -326,16 +336,6 @@ class AgentflowTool extends StructuredTool {
body: JSON.stringify(body)
}
let sandbox = {
$callOptions: options,
$callBody: body,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
const code = `
const fetch = require('node-fetch');
const url = "${this.baseURL}/api/v1/prediction/${this.agentflowid}";
@ -353,26 +353,19 @@ try {
return '';
}
`
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const vmOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: options,
$callBody: body
}
const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
const response = await executeJavaScriptCode(code, sandbox, {
useSandbox: false,
timeout: 10000
} as any
const vm = new NodeVM(vmOptions)
const response = await vm.run(`module.exports = async function() {${code}}()`, __dirname)
})
return response
}

View File

@ -1,11 +1,11 @@
import { DataSource } from 'typeorm'
import { z } from 'zod'
import { NodeVM } from '@flowiseai/nodevm'
import { RunnableConfig } from '@langchain/core/runnables'
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
import { StructuredTool } from '@langchain/core/tools'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { availableDependencies, defaultAllowBuiltInDep, getCredentialData, getCredentialParam } from '../../../src/utils'
import { getCredentialData, getCredentialParam, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { isValidUUID, isValidURL } from '../../../src/validator'
import { v4 as uuidv4 } from 'uuid'
class ChatflowTool_Tools implements INode {
@ -168,6 +168,16 @@ class ChatflowTool_Tools implements INode {
const baseURL = (nodeData.inputs?.baseURL as string) || (options.baseURL as string)
// Validate selectedChatflowId is a valid UUID
if (!selectedChatflowId || !isValidUUID(selectedChatflowId)) {
throw new Error('Invalid chatflow ID: must be a valid UUID')
}
// Validate baseURL is a valid URL
if (!baseURL || !isValidURL(baseURL)) {
throw new Error('Invalid base URL: must be a valid URL')
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData)
@ -334,16 +344,6 @@ class ChatflowTool extends StructuredTool {
body: JSON.stringify(body)
}
let sandbox = {
$callOptions: options,
$callBody: body,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
const code = `
const fetch = require('node-fetch');
const url = "${this.baseURL}/api/v1/prediction/${this.chatflowid}";
@ -361,26 +361,19 @@ try {
return '';
}
`
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const vmOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: options,
$callBody: body
}
const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
const response = await executeJavaScriptCode(code, sandbox, {
useSandbox: false,
timeout: 10000
} as any
const vm = new NodeVM(vmOptions)
const response = await vm.run(`module.exports = async function() {${code}}()`, __dirname)
})
return response
}

View File

@ -1,7 +1,7 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { StructuredTool, ToolInputParsingException, ToolParams } from '@langchain/core/tools'
import { CodeInterpreter } from '@e2b/code-interpreter'
import { Sandbox } from '@e2b/code-interpreter'
import { z } from 'zod'
import { addSingleFileToStorage } from '../../../src/storageUtils'
import { CallbackManager, CallbackManagerForToolRun, Callbacks, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
@ -107,7 +107,7 @@ export class E2BTool extends StructuredTool {
description = DESC
instance: CodeInterpreter
instance: Sandbox
apiKey: string
@ -204,8 +204,8 @@ export class E2BTool extends StructuredTool {
flowConfig = { ...this.flowObj, ...flowConfig }
try {
if ('input' in arg) {
this.instance = await CodeInterpreter.create({ apiKey: this.apiKey })
const execution = await this.instance.notebook.execCell(arg?.input)
this.instance = await Sandbox.create({ apiKey: this.apiKey })
const execution = await this.instance.runCode(arg?.input, { language: 'python' })
const artifacts = []
for (const result of execution.results) {
@ -251,8 +251,6 @@ export class E2BTool extends StructuredTool {
}
}
this.instance.close()
let output = ''
if (execution.text) output = execution.text
@ -267,7 +265,7 @@ export class E2BTool extends StructuredTool {
return 'No input provided'
}
} catch (e) {
if (this.instance) this.instance.close()
if (this.instance) this.instance.kill()
return typeof e === 'string' ? e : JSON.stringify(e, null, 2)
}
}

View File

@ -1,9 +1,8 @@
import { z } from 'zod'
import { NodeVM } from '@flowiseai/nodevm'
import { RunnableConfig } from '@langchain/core/runnables'
import { StructuredTool, ToolParams } from '@langchain/core/tools'
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils'
import { executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ICommonObject } from '../../../src/Interface'
class ToolInputParsingException extends Error {
@ -111,46 +110,23 @@ export class DynamicStructuredTool<
_?: CallbackManagerForToolRun,
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
): Promise<string> {
let sandbox: any = {
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
// Create additional sandbox variables for tool arguments
const additionalSandbox: ICommonObject = {}
if (typeof arg === 'object' && Object.keys(arg).length) {
for (const item in arg) {
sandbox[`$${item}`] = arg[item]
additionalSandbox[`$${item}`] = arg[item]
}
}
sandbox['$vars'] = prepareSandboxVars(this.variables)
// Prepare flow object for sandbox
const flow = this.flowObj ? { ...this.flowObj, ...flowConfig } : {}
// inject flow properties
if (this.flowObj) {
sandbox['$flow'] = { ...this.flowObj, ...flowConfig }
}
const sandbox = createCodeExecutionSandbox('', this.variables || [], flow, additionalSandbox)
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const options = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
const response = await executeJavaScriptCode(this.code, sandbox, {
timeout: 10000
} as any
const vm = new NodeVM(options)
const response = await vm.run(`module.exports = async function() {${this.code}}()`, __dirname)
})
return response
}

View File

@ -1,10 +1,9 @@
import { z } from 'zod'
import { RequestInit } from 'node-fetch'
import { NodeVM } from '@flowiseai/nodevm'
import { RunnableConfig } from '@langchain/core/runnables'
import { StructuredTool, ToolParams } from '@langchain/core/tools'
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils'
import { executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ICommonObject } from '../../../src/Interface'
const removeNulls = (obj: Record<string, any>) => {
@ -217,32 +216,22 @@ export class DynamicStructuredTool<
_?: CallbackManagerForToolRun,
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
): Promise<string> {
let sandbox: any = {
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
let processedArg = { ...arg }
if (this.removeNulls && typeof processedArg === 'object' && processedArg !== null) {
processedArg = removeNulls(processedArg)
}
// Create additional sandbox variables for tool arguments
const additionalSandbox: ICommonObject = {}
if (typeof processedArg === 'object' && Object.keys(processedArg).length) {
for (const item in processedArg) {
sandbox[`$${item}`] = processedArg[item]
additionalSandbox[`$${item}`] = processedArg[item]
}
}
sandbox['$vars'] = prepareSandboxVars(this.variables)
// inject flow properties
if (this.flowObj) {
sandbox['$flow'] = { ...this.flowObj, ...flowConfig }
}
// Prepare HTTP request options
const callOptions: RequestInit = {
method: this.method,
headers: {
@ -253,31 +242,20 @@ export class DynamicStructuredTool<
if (arg.RequestBody && this.method.toUpperCase() !== 'GET') {
callOptions.body = JSON.stringify(arg.RequestBody)
}
sandbox['$options'] = callOptions
additionalSandbox['$options'] = callOptions
// Generate complete URL
const completeUrl = getUrl(this.baseUrl, arg)
sandbox['$url'] = completeUrl
additionalSandbox['$url'] = completeUrl
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
// Prepare flow object for sandbox
const flow = this.flowObj ? { ...this.flowObj, ...flowConfig } : {}
const options = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
const sandbox = createCodeExecutionSandbox('', this.variables || [], flow, additionalSandbox)
const response = await executeJavaScriptCode(this.customCode || defaultCode, sandbox, {
timeout: 10000
} as any
const vm = new NodeVM(options)
const response = await vm.run(`module.exports = async function() {${this.customCode || defaultCode}}()`, __dirname)
})
return response
}

View File

@ -1,8 +1,7 @@
import { flatten } from 'lodash'
import { type StructuredTool } from '@langchain/core/tools'
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
import { getVars, handleEscapeCharacters, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
class CustomFunction_Utilities implements INode {
@ -118,45 +117,24 @@ class CustomFunction_Utilities implements INode {
}
}
let sandbox: any = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$tools: tools
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
sandbox['$tools'] = tools
// Add input variables to sandbox
if (Object.keys(inputVars).length) {
for (const item in inputVars) {
sandbox[`$${item}`] = inputVars[item]
additionalSandbox[`$${item}`] = inputVars[item]
}
}
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
timeout: 10000
})
if (typeof response === 'string' && !isEndingNode) {
return handleEscapeCharacters(response, false)

View File

@ -1,6 +1,5 @@
import { NodeVM } from '@flowiseai/nodevm'
import { DataSource } from 'typeorm'
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
import { getVars, handleEscapeCharacters, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
class IfElseFunction_Utilities implements INode {
@ -119,48 +118,30 @@ class IfElseFunction_Utilities implements INode {
}
}
let sandbox: any = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {}
// Add input variables to sandbox
if (Object.keys(inputVars).length) {
for (const item in inputVars) {
sandbox[`$${item}`] = inputVars[item]
additionalSandbox[`$${item}`] = inputVars[item]
}
}
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
const nodeVMOptions = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout: 10000
} as any
const vm = new NodeVM(nodeVMOptions)
try {
const responseTrue = await vm.run(`module.exports = async function() {${ifFunction}}()`, __dirname)
const responseTrue = await executeJavaScriptCode(ifFunction, sandbox, {
timeout: 10000
})
if (responseTrue)
return { output: typeof responseTrue === 'string' ? handleEscapeCharacters(responseTrue, false) : responseTrue, type: true }
const responseFalse = await vm.run(`module.exports = async function() {${elseFunction}}()`, __dirname)
const responseFalse = await executeJavaScriptCode(elseFunction, sandbox, {
timeout: 10000
})
return { output: typeof responseFalse === 'string' ? handleEscapeCharacters(responseFalse, false) : responseFalse, type: false }
} catch (e) {
throw new Error(e)

View File

@ -26,7 +26,7 @@
"@aws-sdk/client-secrets-manager": "^3.699.0",
"@datastax/astra-db-ts": "1.5.0",
"@dqbd/tiktoken": "^1.0.21",
"@e2b/code-interpreter": "^0.0.5",
"@e2b/code-interpreter": "^1.5.1",
"@elastic/elasticsearch": "^8.9.0",
"@flowiseai/nodevm": "^3.9.25",
"@getzep/zep-cloud": "~1.0.7",

View File

@ -16,6 +16,8 @@ import { GetSecretValueCommand, SecretsManagerClient, SecretsManagerClientConfig
import { customGet } from '../nodes/sequentialagents/commonUtils'
import { TextSplitter } from 'langchain/text_splitter'
import { DocumentLoader } from 'langchain/document_loaders/base'
import { NodeVM } from '@flowiseai/nodevm'
import { Sandbox } from '@e2b/code-interpreter'
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
@ -1348,3 +1350,227 @@ export const stripHTMLFromToolInput = (input: string) => {
cleanedInput = cleanedInput.replace(/\\_/g, '_')
return cleanedInput
}
// Helper function to convert require statements to ESM imports
const convertRequireToImport = (requireLine: string): string | null => {
// Remove leading/trailing whitespace and get the indentation
const indent = requireLine.match(/^(\s*)/)?.[1] || ''
const trimmed = requireLine.trim()
// Match patterns like: const/let/var name = require('module')
const defaultRequireMatch = trimmed.match(/^(const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/)
if (defaultRequireMatch) {
const [, , varName, moduleName] = defaultRequireMatch
return `${indent}import ${varName} from '${moduleName}';`
}
// Match patterns like: const { name1, name2 } = require('module')
const destructureMatch = trimmed.match(/^(const|let|var)\s+\{\s*([^}]+)\s*\}\s*=\s*require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/)
if (destructureMatch) {
const [, , destructuredVars, moduleName] = destructureMatch
return `${indent}import { ${destructuredVars.trim()} } from '${moduleName}';`
}
// Match patterns like: const name = require('module').property
const propertyMatch = trimmed.match(/^(const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)\.(\w+)/)
if (propertyMatch) {
const [, , varName, moduleName, property] = propertyMatch
return `${indent}import { ${property} as ${varName} } from '${moduleName}';`
}
// If no pattern matches, return null to skip conversion
return null
}
/**
* Execute JavaScript code using either Sandbox or NodeVM
* @param {string} code - The JavaScript code to execute
* @param {ICommonObject} sandbox - The sandbox object with variables
* @param {ICommonObject} options - Execution options
* @returns {Promise<any>} - The execution result
*/
export const executeJavaScriptCode = async (
code: string,
sandbox: ICommonObject,
options: {
timeout?: number
useSandbox?: boolean
libraries?: string[]
streamOutput?: (output: string) => void
nodeVMOptions?: ICommonObject
} = {}
): Promise<any> => {
const { timeout = 10000, useSandbox = true, streamOutput, libraries = [], nodeVMOptions = {} } = options
const shouldUseSandbox = useSandbox && process.env.E2B_APIKEY
if (shouldUseSandbox) {
try {
const variableDeclarations = []
if (sandbox['$vars']) {
variableDeclarations.push(`const $vars = ${JSON.stringify(sandbox['$vars'])};`)
}
if (sandbox['$flow']) {
variableDeclarations.push(`const $flow = ${JSON.stringify(sandbox['$flow'])};`)
}
// Add other sandbox variables
for (const [key, value] of Object.entries(sandbox)) {
if (
key !== '$vars' &&
key !== '$flow' &&
key !== 'util' &&
key !== 'Symbol' &&
key !== 'child_process' &&
key !== 'fs' &&
key !== 'process'
) {
variableDeclarations.push(`const ${key} = ${JSON.stringify(value)};`)
}
}
// Handle import statements properly - they must be at the top
const lines = code.split('\n')
const importLines = []
const otherLines = []
for (const line of lines) {
const trimmedLine = line.trim()
// Skip node-fetch imports since Node.js has built-in fetch
if (trimmedLine.includes('node-fetch') || trimmedLine.includes("'fetch'") || trimmedLine.includes('"fetch"')) {
continue // Skip this line entirely
}
// Check for existing ES6 imports and exports
if (trimmedLine.startsWith('import ') || trimmedLine.startsWith('export ')) {
importLines.push(line)
}
// Check for CommonJS require statements and convert them to ESM imports
else if (/^(const|let|var)\s+.*=\s*require\s*\(/.test(trimmedLine)) {
const convertedImport = convertRequireToImport(trimmedLine)
if (convertedImport) {
importLines.push(convertedImport)
}
} else {
otherLines.push(line)
}
}
const sbx = await Sandbox.create({ apiKey: process.env.E2B_APIKEY })
// Install libraries
for (const library of libraries) {
await sbx.commands.run(`npm install ${library}`)
}
// Separate imports from the rest of the code for proper ES6 module structure
const codeWithImports = [
...importLines,
`module.exports = async function() {`,
...variableDeclarations,
...otherLines,
`}()`
].join('\n')
const execution = await sbx.runCode(codeWithImports, { language: 'js' })
let output = ''
if (execution.text) output = execution.text
if (!execution.text && execution.logs.stdout.length) output = execution.logs.stdout.join('\n')
if (execution.error) {
throw new Error(`${execution.error.name}: ${execution.error.value}`)
}
if (execution.logs.stderr.length) {
throw new Error(execution.logs.stderr.join('\n'))
}
// Stream output if streaming function provided
if (streamOutput && output) {
streamOutput(output)
}
// Clean up sandbox
sbx.kill()
return output
} catch (e) {
throw new Error(`Sandbox Execution Error: ${e}`)
}
} else {
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
: defaultAllowBuiltInDep
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
const deps = availableDependencies.concat(externalDeps)
const defaultNodeVMOptions: any = {
console: 'inherit',
sandbox,
require: {
external: { modules: deps },
builtin: builtinDeps
},
eval: false,
wasm: false,
timeout
}
// Merge with custom nodeVMOptions if provided
const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions }
const vm = new NodeVM(finalNodeVMOptions)
try {
const response = await vm.run(`module.exports = async function() {${code}}()`, __dirname)
let finalOutput = response
if (typeof response === 'object') {
finalOutput = JSON.stringify(response, null, 2)
}
// Stream output if streaming function provided
if (streamOutput && finalOutput) {
streamOutput(finalOutput)
}
return finalOutput
} catch (e) {
throw new Error(`NodeVM Execution Error: ${e}`)
}
}
}
/**
* Create a standard sandbox object for code execution
* @param {string} input - The input string
* @param {ICommonObject} variables - Variables from getVars
* @param {ICommonObject} flow - Flow object with chatflowId, sessionId, etc.
* @param {ICommonObject} additionalSandbox - Additional sandbox variables
* @returns {ICommonObject} - The sandbox object
*/
export const createCodeExecutionSandbox = (
input: string,
variables: IVariable[],
flow: ICommonObject,
additionalSandbox: ICommonObject = {}
): ICommonObject => {
const sandbox: ICommonObject = {
$input: input,
util: undefined,
Symbol: undefined,
child_process: undefined,
fs: undefined,
process: undefined,
...additionalSandbox
}
sandbox['$vars'] = prepareSandboxVars(variables)
sandbox['$flow'] = flow
return sandbox
}

View File

@ -9,6 +9,20 @@ export const isValidUUID = (uuid: string): boolean => {
return uuidV4Pattern.test(uuid)
}
/**
* Validates if a string is a valid URL
* @param {string} url The string to validate
* @returns {boolean} True if valid URL, false otherwise
*/
export const isValidURL = (url: string): boolean => {
try {
new URL(url)
return true
} catch {
return false
}
}
/**
* Validates if a string contains path traversal attempts
* @param {string} path The string to validate

View File

@ -183,7 +183,7 @@ const LoaderConfigPreviewChunks = () => {
const previewResp = await documentStoreApi.previewChunks(config)
if (previewResp.data) {
setTotalChunks(previewResp.data.totalChunks)
setDocumentChunks(previewResp.data.chunks)
setDocumentChunks(Array.isArray(previewResp.data.chunks) ? previewResp.data.chunks : [])
setCurrentPreviewCount(previewResp.data.previewChunkCount)
}
setLoading(false)

View File

@ -146,8 +146,8 @@ importers:
specifier: ^1.0.21
version: 1.0.21
'@e2b/code-interpreter':
specifier: ^0.0.5
version: 0.0.5(bufferutil@4.0.8)(utf-8-validate@6.0.4)
specifier: ^1.5.1
version: 1.5.1
'@elastic/elasticsearch':
specifier: ^8.9.0
version: 8.12.2
@ -2989,6 +2989,9 @@ packages:
openai: 4.96.0
zod: ^3.23.8
'@bufbuild/protobuf@2.6.1':
resolution: { integrity: sha512-DaG6XlyKpz08bmHY5SGX2gfIllaqtDJ/KwVoxsmP22COOLYwDBe7yD3DZGwXem/Xq7QOc9cuR7R3MpAv5CFfDw== }
'@bull-board/api@6.11.0':
resolution: { integrity: sha512-HLbIuXIthrgeVRmN7Vec9/7ZKWx8i1xTC6Nzi//l7ua+Xu5wn6f/aZllUNVzty5ilLTHqWFkfVOwpuN91o7yxA== }
peerDependencies:
@ -3058,6 +3061,17 @@ packages:
resolution: { integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== }
engines: { node: '>=0.1.90' }
'@connectrpc/connect-web@2.0.0-rc.3':
resolution: { integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw== }
peerDependencies:
'@bufbuild/protobuf': ^2.2.0
'@connectrpc/connect': 2.0.0-rc.3
'@connectrpc/connect@2.0.0-rc.3':
resolution: { integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ== }
peerDependencies:
'@bufbuild/protobuf': ^2.2.0
'@couchbase/couchbase-darwin-arm64-napi@4.4.1':
resolution: { integrity: sha512-YHS0TDrXe+S3nKywjZ/11Ia+25l5DypXYdyeOf4/le+zsJomnR5E3w4h1RaZ669/gvR8ltyOJn6TG//VDSp6qg== }
engines: { node: '>=16' }
@ -3218,8 +3232,8 @@ packages:
'@dqbd/tiktoken@1.0.21':
resolution: { integrity: sha512-grBxRSY9+/iBM205EWjbMm5ySeXQrhJyXWMP38VJd+pO2DRGraDAbi4n8J8T9M4XY1M/FHgonMcmu3J+KjcX0Q== }
'@e2b/code-interpreter@0.0.5':
resolution: { integrity: sha512-ToFQ6N6EU8t91z3EJzh+mG+zf7uK8I1PRLWeu1f3bPS0pAJfM0puzBWOB3XlF9A9R5zZu/g8iO1gGPQXzXY5vA== }
'@e2b/code-interpreter@1.5.1':
resolution: { integrity: sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw== }
engines: { node: '>=18' }
'@elastic/elasticsearch@8.12.2':
@ -9115,6 +9129,9 @@ packages:
commondir@1.0.1:
resolution: { integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== }
compare-versions@6.1.1:
resolution: { integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg== }
component-emitter@1.3.1:
resolution: { integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== }
@ -9986,8 +10003,8 @@ packages:
duplexify@4.1.3:
resolution: { integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA== }
e2b@0.16.1:
resolution: { integrity: sha512-2L1R/REEB+EezD4Q4MmcXXNATjvCYov2lv/69+PY6V95+wl1PZblIMTYAe7USxX6P6sqANxNs+kXqZr6RvXkSw== }
e2b@1.9.0:
resolution: { integrity: sha512-MM3RhWW7YENYocTy20BvKVcn8li/FxkDrHINS7tmz00ffl1ZavQTRxCI9Sl8ofeRg+HVMlqO4W8LJ+ij9VTZPg== }
engines: { node: '>=18' }
each-props@1.3.2:
@ -11992,11 +12009,6 @@ packages:
isomorphic-fetch@3.0.0:
resolution: { integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== }
isomorphic-ws@5.0.0:
resolution: { integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== }
peerDependencies:
ws: 8.18.3
isstream@0.1.2:
resolution: { integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== }
@ -13919,12 +13931,14 @@ packages:
zod:
optional: true
openapi-fetch@0.9.8:
resolution: { integrity: sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg== }
openapi-types@12.1.3:
resolution: { integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== }
openapi-typescript-fetch@1.1.3:
resolution: { integrity: sha512-smLZPck4OkKMNExcw8jMgrMOGgVGx2N/s6DbKL2ftNl77g5HfoGpZGFy79RBzU/EkaO0OZpwBnslfdBfh7ZcWg== }
engines: { node: '>= 12.0.0', npm: '>= 7.0.0' }
openapi-typescript-helpers@0.0.8:
resolution: { integrity: sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g== }
option@0.2.4:
resolution: { integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A== }
@ -14135,9 +14149,6 @@ packages:
resolution: { integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== }
engines: { node: '>= 0.4.0' }
path-browserify@1.0.1:
resolution: { integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== }
path-case@3.0.4:
resolution: { integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg== }
@ -21782,6 +21793,8 @@ snapshots:
- encoding
- utf-8-validate
'@bufbuild/protobuf@2.6.1': {}
'@bull-board/api@6.11.0(@bull-board/ui@6.11.0)':
dependencies:
'@bull-board/ui': 6.11.0
@ -21911,6 +21924,15 @@ snapshots:
'@colors/colors@1.6.0': {}
'@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.6.1)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.1))':
dependencies:
'@bufbuild/protobuf': 2.6.1
'@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.6.1)
'@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.1)':
dependencies:
'@bufbuild/protobuf': 2.6.1
'@couchbase/couchbase-darwin-arm64-napi@4.4.1':
optional: true
@ -22063,14 +22085,9 @@ snapshots:
'@dqbd/tiktoken@1.0.21': {}
'@e2b/code-interpreter@0.0.5(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
'@e2b/code-interpreter@1.5.1':
dependencies:
e2b: 0.16.1
isomorphic-ws: 5.0.0(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
e2b: 1.9.0
'@elastic/elasticsearch@8.12.2':
dependencies:
@ -29311,6 +29328,8 @@ snapshots:
commondir@1.0.1: {}
compare-versions@6.1.1: {}
component-emitter@1.3.1: {}
component-register@0.8.3: {}
@ -30259,17 +30278,14 @@ snapshots:
readable-stream: 3.6.2
stream-shift: 1.0.3
e2b@0.16.1:
e2b@1.9.0:
dependencies:
isomorphic-ws: 5.0.0(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
normalize-path: 3.0.0
openapi-typescript-fetch: 1.1.3
path-browserify: 1.0.1
'@bufbuild/protobuf': 2.6.1
'@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.6.1)
'@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.6.1)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.1))
compare-versions: 6.1.1
openapi-fetch: 0.9.8
platform: 1.3.6
ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
optionalDependencies:
bufferutil: 4.0.8
utf-8-validate: 6.0.4
each-props@1.3.2:
dependencies:
@ -32981,10 +32997,6 @@ snapshots:
transitivePeerDependencies:
- encoding
isomorphic-ws@5.0.0(ws@8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)):
dependencies:
ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
isstream@0.1.2: {}
istanbul-lib-coverage@3.2.2: {}
@ -35828,9 +35840,13 @@ snapshots:
transitivePeerDependencies:
- encoding
openapi-fetch@0.9.8:
dependencies:
openapi-typescript-helpers: 0.0.8
openapi-types@12.1.3: {}
openapi-typescript-fetch@1.1.3: {}
openapi-typescript-helpers@0.0.8: {}
option@0.2.4: {}
@ -36080,8 +36096,6 @@ snapshots:
pause: 0.0.1
utils-merge: 1.0.1
path-browserify@1.0.1: {}
path-case@3.0.4:
dependencies:
dot-case: 3.0.4