VYPR
High severityNVD Advisory· Published Apr 9, 2025· Updated Apr 9, 2025

CVE-2025-29189

CVE-2025-29189

Description

Flowise <= 2.2.3 is vulnerable to SQL Injection. via tableName parameter at Postgres_VectorStores.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Flowise <= 2.2.3 is vulnerable to SQL injection via the tableName parameter in Postgres_VectorStores, allowing attackers to manipulate database queries.

Vulnerability

Overview CVE-2025-29189 affects Flowise versions up to and including 2.2.3. The vulnerability is a SQL injection flaw located in the Postgres_VectorStores component. The root cause is the lack of sanitization of the tableName parameter before it is interpolated into SQL queries [1][2].

Exploitation

Details An attacker can exploit this vulnerability by providing a maliciously crafted table name through the tableName parameter. Since the parameter is directly concatenated into SQL statements without proper validation or parameterization, the attacker can inject arbitrary SQL commands [2]. The attack requires access to the Postgres vector store functionality, which may be exposed through the application's API or user interface.

Impact

Successful exploitation could allow an attacker to read, modify, or delete arbitrary data in the underlying PostgreSQL database. This could lead to unauthorized access to sensitive information, data corruption, or complete compromise of the database. Given the potential for full database manipulation, the impact is severe.

Mitigation

The vulnerability has been patched in commit 9a417bdc95f58d6dd92cbf60dad42414aba34754, which introduces a sanitizeTableName function that validates input against a strict regex (/^[a-zA-Z0-9_]+$/), allowing only alphanumeric characters and underscores [2]. Users are strongly advised to upgrade to a version containing this fix. No workarounds have been documented.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
flowise-componentsnpm
< 2.2.42.2.4

Affected products

3

Patches

1
9a417bdc95f5

Bugfix/update nodevm sandbox options, sanitize tablename (#3818)

https://github.com/FlowiseAI/FlowiseHenry HengJan 7, 2025via ghsa
16 files changed · +269 76
  • packages/components/nodes/documentloaders/CustomDocumentLoader/CustomDocumentLoader.ts+12 2 modified
    @@ -106,7 +106,14 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
                 }
             }
     
    -        let sandbox: any = { $input: input }
    +        let sandbox: any = {
    +            $input: input,
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
             sandbox['$vars'] = prepareSandboxVars(variables)
             sandbox['$flow'] = flow
     
    @@ -128,7 +135,10 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(nodeVMOptions)
    
  • packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/mysqlSaver.ts+23 6 modified
    @@ -19,6 +19,18 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
             this.threadId = threadId
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { datasourceOptions } = this.config
             if (!datasourceOptions) {
    @@ -38,8 +50,9 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
                 await queryRunner.manager.query(`
    -                CREATE TABLE IF NOT EXISTS ${this.tableName} (
    +                CREATE TABLE IF NOT EXISTS ${tableName} (
                         thread_id VARCHAR(255) NOT NULL,
                         checkpoint_id VARCHAR(255) NOT NULL,
                         parent_id VARCHAR(255),
    @@ -62,12 +75,13 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
     
             const thread_id = config.configurable?.thread_id || this.threadId
             const checkpoint_id = config.configurable?.checkpoint_id
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
                 const sql = checkpoint_id
    -                ? `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = ? AND checkpoint_id = ?`
    -                : `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
    +                ? `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = ? AND checkpoint_id = ?`
    +                : `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
     
                 const rows = await queryRunner.manager.query(sql, checkpoint_id ? [thread_id, checkpoint_id] : [thread_id])
                 await queryRunner.release()
    @@ -108,7 +122,8 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
             const queryRunner = dataSource.createQueryRunner()
             try {
                 const threadId = config.configurable?.thread_id || this.threadId
    -            let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ${
    +            const tableName = this.sanitizeTableName(this.tableName)
    +            let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ${
                     before ? 'AND checkpoint_id < ?' : ''
                 } ORDER BY checkpoint_id DESC`
                 if (limit) {
    @@ -163,8 +178,9 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
                     Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
                     Buffer.from(this.serde.stringify(metadata)) // Encode to binary
                 ]
    +            const tableName = this.sanitizeTableName(this.tableName)
     
    -            const query = `INSERT INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
    +            const query = `INSERT INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
                                VALUES (?, ?, ?, ?, ?)
                                ON DUPLICATE KEY UPDATE checkpoint = VALUES(checkpoint), metadata = VALUES(metadata)`
     
    @@ -190,10 +206,11 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
     
             const dataSource = await this.getDataSource()
             await this.setup(dataSource)
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    -            const query = `DELETE FROM ${this.tableName} WHERE thread_id = ?;`
    +            const query = `DELETE FROM ${tableName} WHERE thread_id = ?;`
                 await queryRunner.manager.query(query, [threadId])
                 await queryRunner.release()
             } catch (error) {
    
  • packages/components/nodes/memory/AgentMemory/PostgresAgentMemory/pgSaver.ts+29 12 modified
    @@ -19,6 +19,18 @@ export class PostgresSaver extends BaseCheckpointSaver implements MemoryMethods
             this.threadId = threadId
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { datasourceOptions } = this.config
             if (!datasourceOptions) {
    @@ -40,8 +52,9 @@ export class PostgresSaver extends BaseCheckpointSaver implements MemoryMethods
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
                 await queryRunner.manager.query(`
    -CREATE TABLE IF NOT EXISTS ${this.tableName} (
    +CREATE TABLE IF NOT EXISTS ${tableName} (
         thread_id TEXT NOT NULL,
         checkpoint_id TEXT NOT NULL,
         parent_id TEXT,
    @@ -63,12 +76,13 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const thread_id = config.configurable?.thread_id || this.threadId
             const checkpoint_id = config.configurable?.checkpoint_id
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             if (checkpoint_id) {
                 try {
                     const queryRunner = dataSource.createQueryRunner()
                     const keys = [thread_id, checkpoint_id]
    -                const sql = `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = $1 AND checkpoint_id = $2`
    +                const sql = `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = $1 AND checkpoint_id = $2`
     
                     const rows = await queryRunner.manager.query(sql, keys)
                     await queryRunner.release()
    @@ -89,16 +103,16 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                         }
                     }
                 } catch (error) {
    -                console.error(`Error retrieving ${this.tableName}`, error)
    -                throw new Error(`Error retrieving ${this.tableName}`)
    +                console.error(`Error retrieving ${tableName}`, error)
    +                throw new Error(`Error retrieving ${tableName}`)
                 } finally {
                     await dataSource.destroy()
                 }
             } else {
                 try {
                     const queryRunner = dataSource.createQueryRunner()
                     const keys = [thread_id]
    -                const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = $1 ORDER BY checkpoint_id DESC LIMIT 1`
    +                const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = $1 ORDER BY checkpoint_id DESC LIMIT 1`
     
                     const rows = await queryRunner.manager.query(sql, keys)
                     await queryRunner.release()
    @@ -124,8 +138,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                         }
                     }
                 } catch (error) {
    -                console.error(`Error retrieving ${this.tableName}`, error)
    -                throw new Error(`Error retrieving ${this.tableName}`)
    +                console.error(`Error retrieving ${tableName}`, error)
    +                throw new Error(`Error retrieving ${tableName}`)
                 } finally {
                     await dataSource.destroy()
                 }
    @@ -139,7 +153,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const queryRunner = dataSource.createQueryRunner()
             const thread_id = config.configurable?.thread_id || this.threadId
    -        let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = $1`
    +        const tableName = this.sanitizeTableName(this.tableName)
    +        let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = $1`
             const args = [thread_id]
     
             if (before?.configurable?.checkpoint_id) {
    @@ -179,8 +194,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                     }
                 }
             } catch (error) {
    -            console.error(`Error listing ${this.tableName}`, error)
    -            throw new Error(`Error listing ${this.tableName}`)
    +            console.error(`Error listing ${tableName}`, error)
    +            throw new Error(`Error listing ${tableName}`)
             } finally {
                 await dataSource.destroy()
             }
    @@ -200,8 +215,9 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                     Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
                     Buffer.from(this.serde.stringify(metadata)) // Encode to binary
                 ]
    +            const tableName = this.sanitizeTableName(this.tableName)
     
    -            const query = `INSERT INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
    +            const query = `INSERT INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
                                VALUES ($1, $2, $3, $4, $5)
                                ON CONFLICT (thread_id, checkpoint_id)
                                DO UPDATE SET checkpoint = EXCLUDED.checkpoint, metadata = EXCLUDED.metadata`
    @@ -230,8 +246,9 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const dataSource = await this.getDataSource()
             await this.setup(dataSource)
    +        const tableName = this.sanitizeTableName(this.tableName)
     
    -        const query = `DELETE FROM "${this.tableName}" WHERE thread_id = $1;`
    +        const query = `DELETE FROM "${tableName}" WHERE thread_id = $1;`
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    
  • packages/components/nodes/memory/AgentMemory/SQLiteAgentMemory/sqliteSaver.ts+29 14 modified
    @@ -19,6 +19,18 @@ export class SqliteSaver extends BaseCheckpointSaver implements MemoryMethods {
             this.threadId = threadId
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { datasourceOptions } = this.config
             const dataSource = new DataSource(datasourceOptions)
    @@ -33,8 +45,9 @@ export class SqliteSaver extends BaseCheckpointSaver implements MemoryMethods {
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
                 await queryRunner.manager.query(`
    -CREATE TABLE IF NOT EXISTS ${this.tableName} (
    +CREATE TABLE IF NOT EXISTS ${tableName} (
         thread_id TEXT NOT NULL,
         checkpoint_id TEXT NOT NULL,
         parent_id TEXT,
    @@ -56,12 +69,13 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const thread_id = config.configurable?.thread_id || this.threadId
             const checkpoint_id = config.configurable?.checkpoint_id
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             if (checkpoint_id) {
                 try {
                     const queryRunner = dataSource.createQueryRunner()
                     const keys = [thread_id, checkpoint_id]
    -                const sql = `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = ? AND checkpoint_id = ?`
    +                const sql = `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = ? AND checkpoint_id = ?`
     
                     const rows = await queryRunner.manager.query(sql, [...keys])
                     await queryRunner.release()
    @@ -82,16 +96,16 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                         }
                     }
                 } catch (error) {
    -                console.error(`Error retrieving ${this.tableName}`, error)
    -                throw new Error(`Error retrieving ${this.tableName}`)
    +                console.error(`Error retrieving ${tableName}`, error)
    +                throw new Error(`Error retrieving ${tableName}`)
                 } finally {
                     await dataSource.destroy()
                 }
             } else {
                 try {
                     const queryRunner = dataSource.createQueryRunner()
                     const keys = [thread_id]
    -                const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
    +                const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
     
                     const rows = await queryRunner.manager.query(sql, [...keys])
                     await queryRunner.release()
    @@ -117,8 +131,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                         }
                     }
                 } catch (error) {
    -                console.error(`Error retrieving ${this.tableName}`, error)
    -                throw new Error(`Error retrieving ${this.tableName}`)
    +                console.error(`Error retrieving ${tableName}`, error)
    +                throw new Error(`Error retrieving ${tableName}`)
                 } finally {
                     await dataSource.destroy()
                 }
    @@ -132,7 +146,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const queryRunner = dataSource.createQueryRunner()
             const thread_id = config.configurable?.thread_id || this.threadId
    -        let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ${
    +        const tableName = this.sanitizeTableName(this.tableName)
    +        let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ${
                 before ? 'AND checkpoint_id < ?' : ''
             } ORDER BY checkpoint_id DESC`
             if (limit) {
    @@ -167,8 +182,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                     }
                 }
             } catch (error) {
    -            console.error(`Error listing ${this.tableName}`, error)
    -            throw new Error(`Error listing ${this.tableName}`)
    +            console.error(`Error listing ${tableName}`, error)
    +            throw new Error(`Error listing ${tableName}`)
             } finally {
                 await dataSource.destroy()
             }
    @@ -188,8 +203,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
                     this.serde.stringify(checkpoint),
                     this.serde.stringify(metadata)
                 ]
    -
    -            const query = `INSERT OR REPLACE INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata) VALUES (?, ?, ?, ?, ?)`
    +            const tableName = this.sanitizeTableName(this.tableName)
    +            const query = `INSERT OR REPLACE INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata) VALUES (?, ?, ?, ?, ?)`
     
                 await queryRunner.manager.query(query, row)
                 await queryRunner.release()
    @@ -215,8 +230,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
     
             const dataSource = await this.getDataSource()
             await this.setup(dataSource)
    -
    -        const query = `DELETE FROM "${this.tableName}" WHERE thread_id = ?;`
    +        const tableName = this.sanitizeTableName(this.tableName)
    +        const query = `DELETE FROM "${tableName}" WHERE thread_id = ?;`
     
             try {
                 const queryRunner = dataSource.createQueryRunner()
    
  • packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts+24 7 modified
    @@ -178,6 +178,18 @@ class MySQLRecordManager implements RecordManagerInterface {
             this.config = config
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { mysqlOptions } = this.config
             if (!mysqlOptions) {
    @@ -196,8 +208,9 @@ class MySQLRecordManager implements RecordManagerInterface {
             try {
                 const dataSource = await this.getDataSource()
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
     
    -            await queryRunner.manager.query(`create table if not exists \`${this.tableName}\` (
    +            await queryRunner.manager.query(`create table if not exists \`${this.sanitizeTableName(tableName)}\` (
                     \`uuid\` varchar(36) primary key default (UUID()),
                     \`key\` varchar(255) not null,
                     \`namespace\` varchar(255) not null,
    @@ -211,11 +224,11 @@ class MySQLRecordManager implements RecordManagerInterface {
                     // MySQL does not support 'IF NOT EXISTS' function for Index
                     const Check = await queryRunner.manager.query(
                         `SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS 
    -                        WHERE table_schema=DATABASE() AND table_name='${this.tableName}' AND index_name='${column}_index';`
    +                        WHERE table_schema=DATABASE() AND table_name='${tableName}' AND index_name='${column}_index';`
                     )
                     if (Check[0].IndexIsThere === 0)
                         await queryRunner.manager.query(`CREATE INDEX \`${column}_index\`
    -        ON \`${this.tableName}\` (\`${column}\`);`)
    +        ON \`${tableName}\` (\`${column}\`);`)
                 }
     
                 await queryRunner.release()
    @@ -253,6 +266,7 @@ class MySQLRecordManager implements RecordManagerInterface {
     
             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 ?? {}
    @@ -275,7 +289,7 @@ class MySQLRecordManager implements RecordManagerInterface {
             ])
     
             const query = `
    -            INSERT INTO \`${this.tableName}\` (\`key\`, \`namespace\`, \`updated_at\`, \`group_id\`)
    +            INSERT INTO \`${tableName}\` (\`key\`, \`namespace\`, \`updated_at\`, \`group_id\`)
                 VALUES (?, ?, ?, ?)
                 ON DUPLICATE KEY UPDATE \`updated_at\` = VALUES(\`updated_at\`)`
     
    @@ -302,12 +316,13 @@ class MySQLRecordManager implements RecordManagerInterface {
     
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             // Prepare the placeholders and the query
             const placeholders = keys.map(() => `?`).join(', ')
             const query = `
         SELECT \`key\`
    -    FROM \`${this.tableName}\`
    +    FROM \`${tableName}\`
         WHERE \`namespace\` = ? AND \`key\` IN (${placeholders})`
     
             // Initialize an array to fill with the existence checks
    @@ -335,10 +350,11 @@ class MySQLRecordManager implements RecordManagerInterface {
         async listKeys(options?: ListKeyOptions): Promise<string[]> {
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             try {
                 const { before, after, limit, groupIds } = options ?? {}
    -            let query = `SELECT \`key\` FROM \`${this.tableName}\` WHERE \`namespace\` = ?`
    +            let query = `SELECT \`key\` FROM \`${tableName}\` WHERE \`namespace\` = ?`
                 const values: (string | number | string[])[] = [this.namespace]
     
                 if (before) {
    @@ -385,9 +401,10 @@ class MySQLRecordManager implements RecordManagerInterface {
     
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             const placeholders = keys.map(() => '?').join(', ')
    -        const query = `DELETE FROM \`${this.tableName}\` WHERE \`namespace\` = ? AND \`key\` IN (${placeholders});`
    +        const query = `DELETE FROM \`${tableName}\` WHERE \`namespace\` = ? AND \`key\` IN (${placeholders});`
             const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
     
             // Directly using try/catch with async/await for cleaner flow
    
  • packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts+27 9 modified
    @@ -186,6 +186,18 @@ class PostgresRecordManager implements RecordManagerInterface {
             this.config = config
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { postgresConnectionOptions } = this.config
             if (!postgresConnectionOptions) {
    @@ -204,20 +216,21 @@ class PostgresRecordManager implements RecordManagerInterface {
             try {
                 const dataSource = await this.getDataSource()
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
     
                 await queryRunner.manager.query(`
    -  CREATE TABLE IF NOT EXISTS "${this.tableName}" (
    +  CREATE TABLE IF NOT EXISTS "${tableName}" (
         uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
         key TEXT NOT NULL,
         namespace TEXT NOT NULL,
         updated_at Double PRECISION NOT NULL,
         group_id TEXT,
         UNIQUE (key, namespace)
       );
    -  CREATE INDEX IF NOT EXISTS updated_at_index ON "${this.tableName}" (updated_at);
    -  CREATE INDEX IF NOT EXISTS key_index ON "${this.tableName}" (key);
    -  CREATE INDEX IF NOT EXISTS namespace_index ON "${this.tableName}" (namespace);
    -  CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
    +  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);`)
     
                 await queryRunner.release()
             } catch (e: any) {
    @@ -269,6 +282,7 @@ class PostgresRecordManager implements RecordManagerInterface {
     
             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 ?? {}
    @@ -287,7 +301,7 @@ class PostgresRecordManager implements RecordManagerInterface {
     
             const valuesPlaceholders = recordsToUpsert.map((_, j) => this.generatePlaceholderForRowAt(j, recordsToUpsert[0].length)).join(', ')
     
    -        const query = `INSERT INTO "${this.tableName}" (key, namespace, updated_at, group_id) VALUES ${valuesPlaceholders} ON CONFLICT (key, namespace) DO UPDATE SET updated_at = EXCLUDED.updated_at;`
    +        const query = `INSERT INTO "${tableName}" (key, namespace, updated_at, group_id) VALUES ${valuesPlaceholders} ON CONFLICT (key, namespace) DO UPDATE SET updated_at = EXCLUDED.updated_at;`
             try {
                 await queryRunner.manager.query(query, recordsToUpsert.flat())
                 await queryRunner.release()
    @@ -306,12 +320,13 @@ class PostgresRecordManager implements RecordManagerInterface {
     
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             const startIndex = 2
             const arrayPlaceholders = keys.map((_, i) => `$${i + startIndex}`).join(', ')
     
             const query = `
    -        SELECT k, (key is not null) ex from unnest(ARRAY[${arrayPlaceholders}]) k left join "${this.tableName}" on k=key and namespace = $1;
    +        SELECT k, (key is not null) ex from unnest(ARRAY[${arrayPlaceholders}]) k left join "${tableName}" on k=key and namespace = $1;
             `
             try {
                 const res = await queryRunner.manager.query(query, [this.namespace, ...keys.flat()])
    @@ -327,7 +342,9 @@ class PostgresRecordManager implements RecordManagerInterface {
     
         async listKeys(options?: ListKeyOptions): Promise<string[]> {
             const { before, after, limit, groupIds } = options ?? {}
    -        let query = `SELECT key FROM "${this.tableName}" WHERE namespace = $1`
    +        const tableName = this.sanitizeTableName(this.tableName)
    +
    +        let query = `SELECT key FROM "${tableName}" WHERE namespace = $1`
             const values: (string | number | (string | null)[])[] = [this.namespace]
     
             let index = 2
    @@ -379,9 +396,10 @@ class PostgresRecordManager implements RecordManagerInterface {
     
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             try {
    -            const query = `DELETE FROM "${this.tableName}" WHERE namespace = $1 AND key = ANY($2);`
    +            const query = `DELETE FROM "${tableName}" WHERE namespace = $1 AND key = ANY($2);`
                 await queryRunner.manager.query(query, [this.namespace, keys])
                 await queryRunner.release()
             } catch (error) {
    
  • packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts+27 9 modified
    @@ -156,6 +156,18 @@ class SQLiteRecordManager implements RecordManagerInterface {
             this.config = config
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         private async getDataSource(): Promise<DataSource> {
             const { sqliteOptions } = this.config
             if (!sqliteOptions) {
    @@ -170,20 +182,21 @@ class SQLiteRecordManager implements RecordManagerInterface {
             try {
                 const dataSource = await this.getDataSource()
                 const queryRunner = dataSource.createQueryRunner()
    +            const tableName = this.sanitizeTableName(this.tableName)
     
                 await queryRunner.manager.query(`
    -CREATE TABLE IF NOT EXISTS "${this.tableName}" (
    +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 "${this.tableName}" (updated_at);
    -CREATE INDEX IF NOT EXISTS key_index ON "${this.tableName}" (key);
    -CREATE INDEX IF NOT EXISTS namespace_index ON "${this.tableName}" (namespace);
    -CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
    +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);`)
     
                 await queryRunner.release()
             } catch (e: any) {
    @@ -219,6 +232,7 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
             }
             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 ?? {}
    @@ -241,7 +255,7 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
             ])
     
             const query = `
    -        INSERT INTO "${this.tableName}" (key, namespace, updated_at, group_id)
    +        INSERT INTO "${tableName}" (key, namespace, updated_at, group_id)
             VALUES (?, ?, ?, ?)
             ON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at`
     
    @@ -264,12 +278,13 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
             if (keys.length === 0) {
                 return []
             }
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             // Prepare the placeholders and the query
             const placeholders = keys.map(() => `?`).join(', ')
             const sql = `
         SELECT key
    -    FROM "${this.tableName}"
    +    FROM "${tableName}"
         WHERE namespace = ? AND key IN (${placeholders})`
     
             // Initialize an array to fill with the existence checks
    @@ -299,7 +314,9 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
     
         async listKeys(options?: ListKeyOptions): Promise<string[]> {
             const { before, after, limit, groupIds } = options ?? {}
    -        let query = `SELECT key FROM "${this.tableName}" WHERE namespace = ?`
    +        const tableName = this.sanitizeTableName(this.tableName)
    +
    +        let query = `SELECT key FROM "${tableName}" WHERE namespace = ?`
             const values: (string | number | string[])[] = [this.namespace]
     
             if (before) {
    @@ -350,9 +367,10 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
     
             const dataSource = await this.getDataSource()
             const queryRunner = dataSource.createQueryRunner()
    +        const tableName = this.sanitizeTableName(this.tableName)
     
             const placeholders = keys.map(() => '?').join(', ')
    -        const query = `DELETE FROM "${this.tableName}" WHERE namespace = ? AND key IN (${placeholders});`
    +        const query = `DELETE FROM "${tableName}" WHERE namespace = ? AND key IN (${placeholders});`
             const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
     
             // Directly using try/catch with async/await for cleaner flow
    
  • packages/components/nodes/sequentialagents/commonUtils.ts+11 2 modified
    @@ -153,7 +153,13 @@ export const processImageMessage = async (llm: BaseChatModel, nodeData: INodeDat
     export const getVM = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData, flow: ICommonObject) => {
         const variables = await getVars(appDataSource, databaseEntities, nodeData)
     
    -    let sandbox: any = {}
    +    let sandbox: any = {
    +        util: undefined,
    +        Symbol: undefined,
    +        child_process: undefined,
    +        fs: undefined,
    +        process: undefined
    +    }
         sandbox['$vars'] = prepareSandboxVars(variables)
         sandbox['$flow'] = flow
     
    @@ -169,7 +175,10 @@ export const getVM = async (appDataSource: DataSource, databaseEntities: IDataba
             require: {
                 external: { modules: deps },
                 builtin: builtinDeps
    -        }
    +        },
    +        eval: false,
    +        wasm: false,
    +        timeout: 10000
         } as any
     
         return new NodeVM(nodeVMOptions)
    
  • packages/components/nodes/sequentialagents/State/State.ts+11 2 modified
    @@ -196,7 +196,13 @@ class State_SeqAgents implements INode {
                     input
                 }
     
    -            let sandbox: any = {}
    +            let sandbox: any = {
    +                util: undefined,
    +                Symbol: undefined,
    +                child_process: undefined,
    +                fs: undefined,
    +                process: undefined
    +            }
                 sandbox['$vars'] = prepareSandboxVars(variables)
                 sandbox['$flow'] = flow
     
    @@ -212,7 +218,10 @@ class State_SeqAgents implements INode {
                     require: {
                         external: { modules: deps },
                         builtin: builtinDeps
    -                }
    +                },
    +                eval: false,
    +                wasm: false,
    +                timeout: 10000
                 } as any
     
                 const vm = new NodeVM(nodeVMOptions)
    
  • packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts+13 2 modified
    @@ -318,7 +318,15 @@ class ChatflowTool extends StructuredTool {
                 body: JSON.stringify(body)
             }
     
    -        let sandbox = { $callOptions: options, $callBody: body }
    +        let sandbox = {
    +            $callOptions: options,
    +            $callBody: body,
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
     
             const code = `
     const fetch = require('node-fetch');
    @@ -349,7 +357,10 @@ try {
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(vmOptions)
    
  • packages/components/nodes/tools/CustomTool/core.ts+11 2 modified
    @@ -111,7 +111,13 @@ export class DynamicStructuredTool<
             _?: CallbackManagerForToolRun,
             flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
         ): Promise<string> {
    -        let sandbox: any = {}
    +        let sandbox: any = {
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
             if (typeof arg === 'object' && Object.keys(arg).length) {
                 for (const item in arg) {
                     sandbox[`$${item}`] = arg[item]
    @@ -137,7 +143,10 @@ export class DynamicStructuredTool<
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(options)
    
  • packages/components/nodes/tools/OpenAPIToolkit/core.ts+11 2 modified
    @@ -196,7 +196,13 @@ export class DynamicStructuredTool<
             _?: CallbackManagerForToolRun,
             flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
         ): Promise<string> {
    -        let sandbox: any = {}
    +        let sandbox: any = {
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
             if (typeof arg === 'object' && Object.keys(arg).length) {
                 for (const item in arg) {
                     sandbox[`$${item}`] = arg[item]
    @@ -237,7 +243,10 @@ export class DynamicStructuredTool<
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(options)
    
  • packages/components/nodes/utilities/CustomFunction/CustomFunction.ts+12 2 modified
    @@ -117,7 +117,14 @@ class CustomFunction_Utilities implements INode {
                 }
             }
     
    -        let sandbox: any = { $input: input }
    +        let sandbox: any = {
    +            $input: input,
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
             sandbox['$vars'] = prepareSandboxVars(variables)
             sandbox['$flow'] = flow
             sandbox['$tools'] = tools
    @@ -140,7 +147,10 @@ class CustomFunction_Utilities implements INode {
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(nodeVMOptions)
    
  • packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts+12 2 modified
    @@ -119,7 +119,14 @@ class IfElseFunction_Utilities implements INode {
                 }
             }
     
    -        let sandbox: any = { $input: input }
    +        let sandbox: any = {
    +            $input: input,
    +            util: undefined,
    +            Symbol: undefined,
    +            child_process: undefined,
    +            fs: undefined,
    +            process: undefined
    +        }
             sandbox['$vars'] = prepareSandboxVars(variables)
             sandbox['$flow'] = flow
     
    @@ -141,7 +148,10 @@ class IfElseFunction_Utilities implements INode {
                 require: {
                     external: { modules: deps },
                     builtin: builtinDeps
    -            }
    +            },
    +            eval: false,
    +            wasm: false,
    +            timeout: 10000
             } as any
     
             const vm = new NodeVM(nodeVMOptions)
    
  • packages/components/nodes/vectorstores/Postgres/driver/Base.ts+13 1 modified
    @@ -28,13 +28,25 @@ export abstract class VectorStoreDriver {
         }
     
         getTableName() {
    -        return getTableName(this.nodeData)
    +        return this.sanitizeTableName(getTableName(this.nodeData))
         }
     
         getEmbeddings() {
             return this.nodeData.inputs?.embeddings as Embeddings
         }
     
    +    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)) {
    +            throw new Error('Invalid table name')
    +        }
    +
    +        return tableName
    +    }
    +
         async getCredentials() {
             const credentialData = await getCredentialData(this.nodeData.credential ?? '', this.options)
             const user = getCredentialParam('user', credentialData, this.nodeData, process.env.POSTGRES_VECTORSTORE_USER)
    
  • packages/components/src/storageUtils.ts+4 2 modified
    @@ -122,10 +122,12 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
     
     export const getFileFromStorage = async (file: string, ...paths: string[]): Promise<Buffer> => {
         const storageType = getStorageType()
    +    const sanitizedFilename = _sanitizeFilename(file)
    +
         if (storageType === 's3') {
             const { s3Client, Bucket } = getS3Config()
     
    -        let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + file
    +        let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + sanitizedFilename
             if (Key.startsWith('/')) {
                 Key = Key.substring(1)
             }
    @@ -147,7 +149,7 @@ export const getFileFromStorage = async (file: string, ...paths: string[]): Prom
             const buffer = Buffer.concat(response.Body.toArray())
             return buffer
         } else {
    -        const fileInStorage = path.join(getStoragePath(), ...paths, file)
    +        const fileInStorage = path.join(getStoragePath(), ...paths, sanitizedFilename)
             return fs.readFileSync(fileInStorage)
         }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.