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

* update nodevm sandbox options, sanitize tablename

* sanitize file name when getFileFromStorage
This commit is contained in:
Henry Heng
2025-01-07 15:26:25 +00:00
committed by GitHub
parent 22801591da
commit 9a417bdc95
16 changed files with 269 additions and 76 deletions
@@ -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
@@ -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,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<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) {
@@ -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,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<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