Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SqlDatabase } from '@langchain/classic/sql_db'
import { ICommonObject, INode, INodeData, INodeParams, IServerSideEventStreamer } from '../../../src/Interface'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
import { getBaseClasses, getInputVariables, transformBracesWithColon } from '../../../src/utils'
import { validateSQLitePath } from '../../../src/validator'
import { checkInputs, Moderation, streamResponse } from '../../moderation/Moderation'
import { formatResponse } from '../../outputparsers/OutputParserHelpers'

Expand Down Expand Up @@ -223,7 +224,7 @@ const getSQLDBChain = async (
databaseType === 'sqlite'
? {
type: databaseType,
database: url
database: validateSQLitePath(url)
}
: ({
type: databaseType,
Expand Down
4 changes: 4 additions & 0 deletions packages/components/nodes/memory/AgentMemory/AgentMemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SqliteSaver } from './SQLiteAgentMemory/sqliteSaver'
import { DataSource } from 'typeorm'
import { PostgresSaver } from './PostgresAgentMemory/pgSaver'
import { MySQLSaver } from './MySQLAgentMemory/mysqlSaver'
import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Import validateSQLitePath to secure the user-provided databaseFilePath against path traversal and arbitrary file write vulnerabilities.

Suggested change
import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
import { validateSQLitePath } from '../../../src/validator'


class AgentMemory_Memory implements INode {
label: string
Expand Down Expand Up @@ -96,6 +97,8 @@ class AgentMemory_Memory implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
}
Expand All @@ -118,6 +121,7 @@ class AgentMemory_Memory implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}
Comment on lines +124 to 125
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The databaseFilePath input is user-controlled and is resolved directly using path.resolve(databaseFilePath) without validation if SQLite is selected. This allows path traversal and arbitrary file write/read vulnerabilities.

Validate databaseFilePath using validateSQLitePath if it is provided.

            additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
        }

        if (databaseType === 'sqlite' && databaseFilePath) {
            validateSQLitePath(databaseFilePath)
        }


const threadId = options.sessionId || options.chatId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SaverOptions } from '../interface'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams } from '../../../../src/Interface'
import { DataSource } from 'typeorm'
import { MySQLSaver } from './mysqlSaver'
import { sanitizeDataSourceOptions } from '../../../../src/sanitizeDataSourceOptions'

class MySQLAgentMemory_Memory implements INode {
label: string
Expand Down Expand Up @@ -54,6 +55,8 @@ class MySQLAgentMemory_Memory implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
}
Expand All @@ -74,6 +77,7 @@ class MySQLAgentMemory_Memory implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const threadId = options.sessionId || options.chatId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SaverOptions } from '../interface'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams } from '../../../../src/Interface'
import { DataSource } from 'typeorm'
import { PostgresSaver } from './pgSaver'
import { sanitizeDataSourceOptions } from '../../../../src/sanitizeDataSourceOptions'

class PostgresAgentMemory_Memory implements INode {
label: string
Expand Down Expand Up @@ -54,6 +55,8 @@ class PostgresAgentMemory_Memory implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
}
Expand All @@ -74,6 +77,7 @@ class PostgresAgentMemory_Memory implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const threadId = options.sessionId || options.chatId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { SaverOptions } from '../interface'
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams } from '../../../../src/Interface'
import { SqliteSaver } from './sqliteSaver'
import { DataSource } from 'typeorm'
import { mergeDataSourceOptions, sanitizeDataSourceOptions } from '../../../../src/sanitizeDataSourceOptions'
import { validateSQLitePath } from '../../../../src/validator'

class SQLiteAgentMemory_Memory implements INode {
label: string
Expand Down Expand Up @@ -40,6 +42,8 @@ class SQLiteAgentMemory_Memory implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
}
Expand All @@ -60,17 +64,20 @@ class SQLiteAgentMemory_Memory implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const threadId = options.sessionId || options.chatId

const database = path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite')
const database = validateSQLitePath(path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite'))

let datasourceOptions: ICommonObject = {
database,
...additionalConfiguration,
type: 'sqlite'
}
const datasourceOptions: ICommonObject = mergeDataSourceOptions(
{
database,
type: 'sqlite'
},
additionalConfiguration
)

const args: SaverOptions = {
datasourceOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { mergeDataSourceOptions, sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
import { sanitizeRecordManagerNamespace, sanitizeRecordManagerTableName } from '../../../src/recordManagerSecurity'
import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchain/community/indexes/base'
import { DataSource } from 'typeorm'

Expand Down Expand Up @@ -47,6 +49,8 @@ class MySQLRecordManager_RecordManager implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
},
Expand Down Expand Up @@ -118,10 +122,10 @@ class MySQLRecordManager_RecordManager implements INode {
const user = getCredentialParam('user', credentialData, nodeData)
const password = getCredentialParam('password', credentialData, nodeData)
const _tableName = nodeData.inputs?.tableName as string
const tableName = _tableName ? _tableName : 'upsertion_records'
const tableName = sanitizeRecordManagerTableName(_tableName ? _tableName : 'upsertion_records')
const additionalConfig = nodeData.inputs?.additionalConfig as string
const _namespace = nodeData.inputs?.namespace as string
const namespace = _namespace ? _namespace : options.chatflowid
const namespace = _namespace ? sanitizeRecordManagerNamespace(_namespace) : options.chatflowid
const cleanup = nodeData.inputs?.cleanup as string
const _sourceIdKey = nodeData.inputs?.sourceIdKey as string
const sourceIdKey = _sourceIdKey ? _sourceIdKey : 'source'
Expand All @@ -133,17 +137,20 @@ class MySQLRecordManager_RecordManager implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const mysqlOptions = {
...additionalConfiguration,
type: 'mysql',
host: nodeData.inputs?.host as string,
port: nodeData.inputs?.port as number,
username: user,
password: password,
database: nodeData.inputs?.database as string
}
const mysqlOptions = mergeDataSourceOptions(
{
type: 'mysql',
host: nodeData.inputs?.host as string,
port: nodeData.inputs?.port as number,
username: user,
password: password,
database: nodeData.inputs?.database as string
},
additionalConfiguration
)

const args = {
mysqlOptions,
Expand Down Expand Up @@ -178,15 +185,7 @@ class MySQLRecordManager 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)) {
throw new Error('Invalid table name')
}

return tableName
return sanitizeRecordManagerTableName(tableName)
}

private async getDataSource(): Promise<DataSource> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchai
import { DataSource } from 'typeorm'
import { getHost, getSSL } from '../../vectorstores/Postgres/utils'
import { getDatabase, getPort, getTableName } from './utils'
import { mergeDataSourceOptions, sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
import { sanitizeRecordManagerNamespace, sanitizeRecordManagerTableName } from '../../../src/recordManagerSecurity'

const serverCredentialsExists = !!process.env.POSTGRES_RECORDMANAGER_USER && !!process.env.POSTGRES_RECORDMANAGER_PASSWORD

Expand Down Expand Up @@ -63,6 +65,8 @@ class PostgresRecordManager_RecordManager implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
},
Expand Down Expand Up @@ -134,10 +138,10 @@ class PostgresRecordManager_RecordManager implements INode {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const user = getCredentialParam('user', credentialData, nodeData, process.env.POSTGRES_RECORDMANAGER_USER)
const password = getCredentialParam('password', credentialData, nodeData, process.env.POSTGRES_RECORDMANAGER_PASSWORD)
const tableName = getTableName(nodeData)
const tableName = sanitizeRecordManagerTableName(getTableName(nodeData))
const additionalConfig = nodeData.inputs?.additionalConfig as string
const _namespace = nodeData.inputs?.namespace as string
const namespace = _namespace ? _namespace : options.chatflowid
const namespace = _namespace ? sanitizeRecordManagerNamespace(_namespace) : options.chatflowid
const cleanup = nodeData.inputs?.cleanup as string
const _sourceIdKey = nodeData.inputs?.sourceIdKey as string
const sourceIdKey = _sourceIdKey ? _sourceIdKey : 'source'
Expand All @@ -149,18 +153,21 @@ class PostgresRecordManager_RecordManager implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const postgresConnectionOptions = {
...additionalConfiguration,
type: 'postgres',
host: getHost(nodeData),
port: getPort(nodeData),
ssl: getSSL(nodeData),
username: user,
password: password,
database: getDatabase(nodeData)
}
const postgresConnectionOptions = mergeDataSourceOptions(
{
type: 'postgres',
host: getHost(nodeData),
port: getPort(nodeData),
ssl: getSSL(nodeData),
username: user,
password: password,
database: getDatabase(nodeData)
},
additionalConfiguration
)

const args = {
postgresConnectionOptions: postgresConnectionOptions,
Expand Down Expand Up @@ -195,15 +202,7 @@ class PostgresRecordManager 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)) {
throw new Error('Invalid table name')
}

return tableName
return sanitizeRecordManagerTableName(tableName)
}

private async getDataSource(): Promise<DataSource> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { getBaseClasses, getUserHome } from '../../../src/utils'
import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchain/community/indexes/base'
import { DataSource } from 'typeorm'
import path from 'path'
import { mergeDataSourceOptions, sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions'
import { sanitizeRecordManagerNamespace, sanitizeRecordManagerTableName } from '../../../src/recordManagerSecurity'
import { validateSQLitePath } from '../../../src/validator'

class SQLiteRecordManager_RecordManager implements INode {
label: string
Expand Down Expand Up @@ -36,6 +39,8 @@ class SQLiteRecordManager_RecordManager implements INode {
label: 'Additional Connection Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
},
Expand Down Expand Up @@ -98,10 +103,10 @@ 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 tableName = sanitizeRecordManagerTableName(_tableName ? _tableName : 'upsertion_records')
const additionalConfig = nodeData.inputs?.additionalConfig as string
const _namespace = nodeData.inputs?.namespace as string
const namespace = _namespace ? _namespace : options.chatflowid
const namespace = _namespace ? sanitizeRecordManagerNamespace(_namespace) : options.chatflowid
const cleanup = nodeData.inputs?.cleanup as string
const _sourceIdKey = nodeData.inputs?.sourceIdKey as string
const sourceIdKey = _sourceIdKey ? _sourceIdKey : 'source'
Expand All @@ -113,15 +118,18 @@ class SQLiteRecordManager_RecordManager implements INode {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

const database = path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite')
const database = validateSQLitePath(path.join(process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise'), 'database.sqlite'))

const sqliteOptions = {
database,
...additionalConfiguration,
type: 'sqlite'
}
const sqliteOptions = mergeDataSourceOptions(
{
database,
type: 'sqlite'
},
additionalConfiguration
)

const args = {
sqliteOptions,
Expand Down Expand Up @@ -156,15 +164,7 @@ 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)) {
throw new Error('Invalid table name')
}

return tableName
return sanitizeRecordManagerTableName(tableName)
}

private async getDataSource(): Promise<DataSource> {
Expand Down
2 changes: 2 additions & 0 deletions packages/components/nodes/vectorstores/Postgres/Postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ class Postgres_VectorStores implements INode {
label: 'Additional Configuration',
name: 'additionalConfig',
type: 'json',
description:
'Optional TypeORM connection options (e.g. ssl, connectTimeout). entities, subscribers, migrations, and extra are not allowed.',
additionalParams: true,
optional: true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { VectorStoreDriver } from './Base'
import { FLOWISE_CHATID } from '../../../../src'
import { sanitizeDataSourceOptions } from '../../../../src/sanitizeDataSourceOptions'
import { DistanceStrategy, PGVectorStore, PGVectorStoreArgs } from '@langchain/community/vectorstores/pgvector'
import { Document } from '@langchain/core/documents'
import { PoolConfig } from 'pg'
Expand All @@ -27,6 +28,7 @@ export class PGVectorDriver extends VectorStoreDriver {
} catch (exception) {
throw new Error('Invalid JSON in the Additional Configuration: ' + exception)
}
additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration)
}

this._postgresConnectionOptions = {
Expand Down
Loading
Loading