fix: FLOWISE-553, 598, 616#6464
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces security enhancements to sanitize TypeORM connection options via sanitizeDataSourceOptions and validate SQLite database paths using validateSQLitePath across multiple database, memory, and vector store nodes. The review feedback highlights critical security and compatibility improvements: using the in operator instead of hasOwnProperty to prevent prototype pollution bypasses, validating databaseFilePath in AgentMemory to block path traversal, adding tests for prototype chain pollution, and handling case-insensitive path comparisons on Windows to avoid false positives.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| for (const key of BLOCKED_DATASOURCE_KEYS) { | ||
| if (Object.prototype.hasOwnProperty.call(config, key)) { | ||
| throw new Error(`Disallowed TypeORM DataSource option: ${key}`) | ||
| } | ||
| } |
There was a problem hiding this comment.
Using Object.prototype.hasOwnProperty.call(config, key) only checks if the property is directly defined on the object itself. If a malicious payload is constructed such that the blocked keys (like entities or subscribers) are defined on the prototype chain (e.g., via prototype pollution or inheritance), this check will be bypassed, but TypeORM will still access and load them.
Using the in operator is much more secure as it inspects the prototype chain as well.
| for (const key of BLOCKED_DATASOURCE_KEYS) { | |
| if (Object.prototype.hasOwnProperty.call(config, key)) { | |
| throw new Error(`Disallowed TypeORM DataSource option: ${key}`) | |
| } | |
| } | |
| for (const key of BLOCKED_DATASOURCE_KEYS) { | |
| if (key in config) { | |
| throw new Error("Disallowed TypeORM DataSource option: " + key) | |
| } | |
| } |
| import { DataSource } from 'typeorm' | ||
| import { PostgresSaver } from './PostgresAgentMemory/pgSaver' | ||
| import { MySQLSaver } from './MySQLAgentMemory/mysqlSaver' | ||
| import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions' |
There was a problem hiding this comment.
Import validateSQLitePath to secure the user-provided databaseFilePath against path traversal and arbitrary file write vulnerabilities.
| import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions' | |
| import { sanitizeDataSourceOptions } from '../../../src/sanitizeDataSourceOptions' | |
| import { validateSQLitePath } from '../../../src/validator' |
| additionalConfiguration = sanitizeDataSourceOptions(additionalConfiguration) | ||
| } |
There was a problem hiding this comment.
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)
}| it.each(['entities', 'subscribers', 'migrations', 'extra'] as const)('throws when %s is present', (key) => { | ||
| expect(() => sanitizeDataSourceOptions({ [key]: ['/tmp/evil.js'] })).toThrow(`Disallowed TypeORM DataSource option: ${key}`) | ||
| }) |
There was a problem hiding this comment.
Add a test case to verify that blocked keys present on the prototype chain are also correctly detected and rejected.
it.each(['entities', 'subscribers', 'migrations', 'extra'] as const)('throws when %s is present', (key) => {
expect(() => sanitizeDataSourceOptions({ [key]: ['/tmp/evil.js'] })).toThrow("Disallowed TypeORM DataSource option: " + key)
})
it('throws when blocked key is present on the prototype chain', () => {
const maliciousConfig = Object.create({ entities: ['/tmp/evil.js'] })
expect(() => sanitizeDataSourceOptions(maliciousConfig)).toThrow('Disallowed TypeORM DataSource option: entities')
})| const normalizedResolved = path.normalize(resolvedPath) | ||
| const normalizedAllowed = path.normalize(allowedDir) | ||
| if (normalizedResolved !== normalizedAllowed && !normalizedResolved.startsWith(normalizedAllowed + path.sep)) { | ||
| throw new Error(`Invalid SQLite path: path must be within ${allowedDir}. Attempted path: ${resolvedPath}`) | ||
| } |
There was a problem hiding this comment.
On Windows, path comparisons are case-insensitive. Since resolvedPath might preserve the casing of the user's input (e.g., lowercase drive letter c:\...) while allowedDir uses the system's casing (e.g., uppercase C:\...), the startsWith check can fail with a false positive, rejecting perfectly valid paths.
Convert both paths to lowercase when running on Windows before performing the comparison.
let normalizedResolved = path.normalize(resolvedPath)
let normalizedAllowed = path.normalize(allowedDir)
if (process.platform === 'win32') {
normalizedResolved = normalizedResolved.toLowerCase()
normalizedAllowed = normalizedAllowed.toLowerCase()
}
if (normalizedResolved !== normalizedAllowed && !normalizedResolved.startsWith(normalizedAllowed + path.sep)) {
throw new Error("Invalid SQLite path: path must be within " + allowedDir + ". Attempted path: " + resolvedPath)
}
No description provided.