diff --git a/packages/components/nodes/documentloaders/CustomDocumentLoader/CustomDocumentLoader.ts b/packages/components/nodes/documentloaders/CustomDocumentLoader/CustomDocumentLoader.ts index 6afac5637d3..ed19a01e731 100644 --- a/packages/components/nodes/documentloaders/CustomDocumentLoader/CustomDocumentLoader.ts +++ b/packages/components/nodes/documentloaders/CustomDocumentLoader/CustomDocumentLoader.ts @@ -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) diff --git a/packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/mysqlSaver.ts b/packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/mysqlSaver.ts index 4214734f53c..5b41b08874b 100644 --- a/packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/mysqlSaver.ts +++ b/packages/components/nodes/memory/AgentMemory/MySQLAgentMemory/mysqlSaver.ts @@ -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 { 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) { diff --git a/packages/components/nodes/memory/AgentMemory/PostgresAgentMemory/pgSaver.ts b/packages/components/nodes/memory/AgentMemory/PostgresAgentMemory/pgSaver.ts index 702c8cfad56..5bc69f663b9 100644 --- a/packages/components/nodes/memory/AgentMemory/PostgresAgentMemory/pgSaver.ts +++ b/packages/components/nodes/memory/AgentMemory/PostgresAgentMemory/pgSaver.ts @@ -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 { 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,8 +103,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() } @@ -98,7 +112,7 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} ( 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() diff --git a/packages/components/nodes/memory/AgentMemory/SQLiteAgentMemory/sqliteSaver.ts b/packages/components/nodes/memory/AgentMemory/SQLiteAgentMemory/sqliteSaver.ts index e38b5bbb3a3..351c7896185 100644 --- a/packages/components/nodes/memory/AgentMemory/SQLiteAgentMemory/sqliteSaver.ts +++ b/packages/components/nodes/memory/AgentMemory/SQLiteAgentMemory/sqliteSaver.ts @@ -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 { 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,8 +96,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() } @@ -91,7 +105,7 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} ( 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() diff --git a/packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts b/packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts index 193c13ba82a..a1571b028c1 100644 --- a/packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts +++ b/packages/components/nodes/recordmanager/MySQLRecordManager/MySQLrecordManager.ts @@ -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 { 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 { 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 diff --git a/packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts b/packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts index 94d154da2c0..63beee92793 100644 --- a/packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts +++ b/packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts @@ -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 { const { postgresConnectionOptions } = this.config if (!postgresConnectionOptions) { @@ -204,9 +216,10 @@ 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, @@ -214,10 +227,10 @@ class PostgresRecordManager implements RecordManagerInterface { 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 { 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) { diff --git a/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts b/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts index 3246e250ef8..c209f4956fc 100644 --- a/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts +++ b/packages/components/nodes/recordmanager/SQLiteRecordManager/SQLiteRecordManager.ts @@ -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 { const { sqliteOptions } = this.config if (!sqliteOptions) { @@ -170,9 +182,10 @@ 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, @@ -180,10 +193,10 @@ CREATE TABLE IF NOT EXISTS "${this.tableName}" ( 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 { 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 diff --git a/packages/components/nodes/sequentialagents/State/State.ts b/packages/components/nodes/sequentialagents/State/State.ts index 7b3ded9bb06..31d7af0c0f7 100644 --- a/packages/components/nodes/sequentialagents/State/State.ts +++ b/packages/components/nodes/sequentialagents/State/State.ts @@ -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) diff --git a/packages/components/nodes/sequentialagents/commonUtils.ts b/packages/components/nodes/sequentialagents/commonUtils.ts index 6133921767d..3fe298f7f04 100644 --- a/packages/components/nodes/sequentialagents/commonUtils.ts +++ b/packages/components/nodes/sequentialagents/commonUtils.ts @@ -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) diff --git a/packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts b/packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts index 85edf6db8e3..06f2451afef 100644 --- a/packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts +++ b/packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts @@ -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) diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index c8883eda7b2..e64f776b8b3 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -111,7 +111,13 @@ export class DynamicStructuredTool< _?: CallbackManagerForToolRun, flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject } ): Promise { - 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) diff --git a/packages/components/nodes/tools/OpenAPIToolkit/core.ts b/packages/components/nodes/tools/OpenAPIToolkit/core.ts index 162be092075..8341adc5400 100644 --- a/packages/components/nodes/tools/OpenAPIToolkit/core.ts +++ b/packages/components/nodes/tools/OpenAPIToolkit/core.ts @@ -196,7 +196,13 @@ export class DynamicStructuredTool< _?: CallbackManagerForToolRun, flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject } ): Promise { - 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) diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index c6f51bc10e9..1bfddefca69 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -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) diff --git a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts index 15100158c90..08b81163652 100644 --- a/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts +++ b/packages/components/nodes/utilities/IfElseFunction/IfElseFunction.ts @@ -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) diff --git a/packages/components/nodes/vectorstores/Postgres/driver/Base.ts b/packages/components/nodes/vectorstores/Postgres/driver/Base.ts index 1616c71f7d4..fc1379f2682 100644 --- a/packages/components/nodes/vectorstores/Postgres/driver/Base.ts +++ b/packages/components/nodes/vectorstores/Postgres/driver/Base.ts @@ -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) diff --git a/packages/components/src/storageUtils.ts b/packages/components/src/storageUtils.ts index 0c354fec1ff..2b7146cde02 100644 --- a/packages/components/src/storageUtils.ts +++ b/packages/components/src/storageUtils.ts @@ -122,10 +122,12 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName: export const getFileFromStorage = async (file: string, ...paths: string[]): Promise => { 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) } }