diff --git a/packages/ai/src/utils/sql-permissions/sql-parser-helpers.ts b/packages/ai/src/utils/sql-permissions/sql-parser-helpers.ts index b36c9c02f..ef6d4e2dc 100644 --- a/packages/ai/src/utils/sql-permissions/sql-parser-helpers.ts +++ b/packages/ai/src/utils/sql-permissions/sql-parser-helpers.ts @@ -1,4 +1,4 @@ -import { Parser } from 'node-sql-parser'; +import { BaseFrom, ColumnRefItem, Join, Parser, type Select } from 'node-sql-parser'; import * as yaml from 'yaml'; export interface ParsedTable { @@ -348,7 +348,10 @@ export function extractTablesFromYml(ymlContent: string): ParsedTable[] { * Validates that wildcards (SELECT *) are not used on physical tables * Allows wildcards on CTEs but blocks them on physical database tables */ -export function validateWildcardUsage(sql: string, dataSourceSyntax?: string): WildcardValidationResult { +export function validateWildcardUsage( + sql: string, + dataSourceSyntax?: string +): WildcardValidationResult { const dialect = getParserDialect(dataSourceSyntax); const parser = new Parser(); @@ -373,18 +376,18 @@ export function validateWildcardUsage(sql: string, dataSourceSyntax?: string): W const tableList = parser.tableList(sql, { database: dialect }); const tableAliasMap = new Map(); // alias -> table name - + if (Array.isArray(tableList)) { for (const tableRef of tableList) { if (typeof tableRef === 'string') { // Simple table name tableAliasMap.set(tableRef.toLowerCase(), tableRef); } else if (tableRef && typeof tableRef === 'object') { - const tableRefObj = tableRef as any; // Type assertion to handle dynamic properties + const tableRefObj = tableRef as Record; const tableName = tableRefObj.table || tableRefObj.name; const alias = tableRefObj.as || tableRefObj.alias; - if (tableName) { - if (alias) { + if (tableName && typeof tableName === 'string') { + if (alias && typeof alias === 'string') { tableAliasMap.set(alias.toLowerCase(), tableName); } tableAliasMap.set(tableName.toLowerCase(), tableName); @@ -395,10 +398,13 @@ export function validateWildcardUsage(sql: string, dataSourceSyntax?: string): W // Check each statement for wildcard usage const blockedTables: string[] = []; - + for (const statement of statements) { if ('type' in statement && statement.type === 'select') { - const wildcardTables = findWildcardUsageOnPhysicalTables(statement, cteNames); + const wildcardTables = findWildcardUsageOnPhysicalTables( + statement as unknown as Record, + cteNames + ); blockedTables.push(...wildcardTables); } } @@ -424,40 +430,48 @@ export function validateWildcardUsage(sql: string, dataSourceSyntax?: string): W /** * Recursively finds wildcard usage on physical tables in a SELECT statement */ -function findWildcardUsageOnPhysicalTables(selectStatement: any, cteNames: Set): string[] { +function findWildcardUsageOnPhysicalTables( + selectStatement: Record, + cteNames: Set +): string[] { const blockedTables: string[] = []; // Build alias mapping for this statement const aliasToTableMap = new Map(); if (selectStatement.from && Array.isArray(selectStatement.from)) { for (const fromItem of selectStatement.from) { - if (fromItem.table && fromItem.as) { + const fromItemAny = fromItem as unknown as Record; + if (fromItemAny.table && fromItemAny.as) { let tableName: string; - if (typeof fromItem.table === 'string') { - tableName = fromItem.table; - } else if (fromItem.table && typeof fromItem.table === 'object') { - const tableObj = fromItem.table as any; - tableName = tableObj.table || tableObj.name || tableObj.value || String(fromItem.table); + if (typeof fromItemAny.table === 'string') { + tableName = fromItemAny.table; + } else if (fromItemAny.table && typeof fromItemAny.table === 'object') { + const tableObj = fromItemAny.table as Record; + tableName = String( + tableObj.table || tableObj.name || tableObj.value || fromItemAny.table + ); } else { continue; } - aliasToTableMap.set(fromItem.as.toLowerCase(), tableName.toLowerCase()); + aliasToTableMap.set(String(fromItemAny.as).toLowerCase(), tableName.toLowerCase()); } - + // Handle JOINs - if (fromItem.join && Array.isArray(fromItem.join)) { - for (const joinItem of fromItem.join) { + if (fromItemAny.join && Array.isArray(fromItemAny.join)) { + for (const joinItem of fromItemAny.join) { if (joinItem.table && joinItem.as) { let tableName: string; if (typeof joinItem.table === 'string') { tableName = joinItem.table; } else if (joinItem.table && typeof joinItem.table === 'object') { - const tableObj = joinItem.table as any; - tableName = tableObj.table || tableObj.name || tableObj.value || String(joinItem.table); + const tableObj = joinItem.table as Record; + tableName = String( + tableObj.table || tableObj.name || tableObj.value || joinItem.table + ); } else { continue; } - aliasToTableMap.set(joinItem.as.toLowerCase(), tableName.toLowerCase()); + aliasToTableMap.set(String(joinItem.as).toLowerCase(), tableName.toLowerCase()); } } } @@ -470,7 +484,10 @@ function findWildcardUsageOnPhysicalTables(selectStatement: any, cteNames: Set[], + cteNames + ); blockedTables.push(...physicalTables); } // Check for qualified wildcard (SELECT table.*) @@ -481,17 +498,19 @@ function findWildcardUsageOnPhysicalTables(selectStatement: any, cteNames: Set; + tableName = String( + tableRefObj.table || tableRefObj.name || tableRefObj.value || column.expr.table + ); } else { continue; // Skip if we can't determine table name } - + // Check if this is an alias that maps to a CTE const actualTableName = aliasToTableMap.get(tableName.toLowerCase()); const isAliasToCte = actualTableName && cteNames.has(actualTableName); const isDirectCte = cteNames.has(tableName.toLowerCase()); - + if (!isAliasToCte && !isDirectCte) { blockedTables.push(tableName); } @@ -503,18 +522,26 @@ function findWildcardUsageOnPhysicalTables(selectStatement: any, cteNames: Set; + if (cteAny.stmt && typeof cteAny.stmt === 'object' && cteAny.stmt !== null) { + const stmt = cteAny.stmt as Record; + if (stmt.type === 'select') { + const subBlocked = findWildcardUsageOnPhysicalTables(stmt, cteNames); + blockedTables.push(...subBlocked); + } } } } if (selectStatement.from && Array.isArray(selectStatement.from)) { for (const fromItem of selectStatement.from) { - if (fromItem.expr && fromItem.expr.type === 'select') { - const subBlocked = findWildcardUsageOnPhysicalTables(fromItem.expr, cteNames); - blockedTables.push(...subBlocked); + const fromItemAny = fromItem as unknown as Record; + if (fromItemAny.expr && typeof fromItemAny.expr === 'object' && fromItemAny.expr !== null) { + const expr = fromItemAny.expr as Record; + if (expr.type === 'select') { + const subBlocked = findWildcardUsageOnPhysicalTables(expr, cteNames); + blockedTables.push(...subBlocked); + } } } } @@ -525,7 +552,10 @@ function findWildcardUsageOnPhysicalTables(selectStatement: any, cteNames: Set): string[] { +function getPhysicalTablesFromFrom( + fromClause: Record[], + cteNames: Set +): string[] { const tables: string[] = []; if (!fromClause || !Array.isArray(fromClause)) { @@ -539,18 +569,18 @@ function getPhysicalTablesFromFrom(fromClause: any[], cteNames: Set): st if (typeof fromItem.table === 'string') { tableName = fromItem.table; } else if (fromItem.table && typeof fromItem.table === 'object') { - const tableObj = fromItem.table as any; - tableName = tableObj.table || tableObj.name || tableObj.value || String(fromItem.table); + const tableObj = fromItem.table as Record; + tableName = String(tableObj.table || tableObj.name || tableObj.value || fromItem.table); } else { continue; } - + if (tableName && !cteNames.has(tableName.toLowerCase())) { const aliasName = fromItem.as || tableName; - tables.push(aliasName); + tables.push(String(aliasName)); } } - + // Handle JOINs if (fromItem.join && Array.isArray(fromItem.join)) { for (const joinItem of fromItem.join) { @@ -559,15 +589,15 @@ function getPhysicalTablesFromFrom(fromClause: any[], cteNames: Set): st if (typeof joinItem.table === 'string') { tableName = joinItem.table; } else if (joinItem.table && typeof joinItem.table === 'object') { - const tableObj = joinItem.table as any; - tableName = tableObj.table || tableObj.name || tableObj.value || String(joinItem.table); + const tableObj = joinItem.table as Record; + tableName = String(tableObj.table || tableObj.name || tableObj.value || joinItem.table); } else { continue; } - + if (tableName && !cteNames.has(tableName.toLowerCase())) { const aliasName = joinItem.as || tableName; - tables.push(aliasName); + tables.push(String(aliasName)); } } }