Simple SQL parser library that converts SQL statements to AST and back to SQL with support for multiple database engines
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Table and column access analysis with whitelist validation for security and authorization checking. This functionality helps enforce access controls and analyze SQL queries for security compliance.
Extract all tables accessed by a SQL query with operation type information.
/**
* Extract list of tables from SQL query
* @param sql - SQL string to analyze
* @param opt - Optional configuration object
* @returns Array of table names with authority format
*/
tableList(sql: string, opt?: Option): string[];Authority Format: {type}::{dbName}::{tableName}
Where:
type is the operation type: select, update, delete, insertdbName is the database name (or null if not specified)tableName is the table nameUsage Examples:
const { Parser } = require('node-sql-parser');
const parser = new Parser();
// Simple table access
const tables1 = parser.tableList('SELECT * FROM users');
console.log(tables1); // ["select::null::users"]
// Multiple tables with joins
const tables2 = parser.tableList(`
SELECT u.name, p.title
FROM users u
JOIN posts p ON u.id = p.user_id
`);
console.log(tables2); // ["select::null::users", "select::null::posts"]
// Database-qualified table names
const tables3 = parser.tableList('SELECT * FROM mydb.users');
console.log(tables3); // ["select::mydb::users"]
// Mixed operations
const tables4 = parser.tableList(`
UPDATE orders SET status = 'shipped'
WHERE user_id IN (SELECT id FROM users WHERE active = 1)
`);
console.log(tables4); // ["update::null::orders", "select::null::users"]
// Schema-qualified names (e.g., PostgreSQL)
const tables5 = parser.tableList('SELECT * FROM public.users', { database: 'PostgreSQL' });
console.log(tables5); // ["select::public::users"]
// Server.database.schema.table (TransactSQL)
const tables6 = parser.tableList(
'SELECT * FROM server1.mydb.dbo.users',
{ database: 'TransactSQL' }
);
console.log(tables6); // ["select::server1.mydb.dbo::users"]Extract all columns accessed by a SQL query with operation and table context.
/**
* Extract list of columns from SQL query
* @param sql - SQL string to analyze
* @param opt - Optional configuration object
* @returns Array of column names with authority format
*/
columnList(sql: string, opt?: Option): string[];Authority Format: {type}::{tableName}::{columnName}
Where:
type is the operation type: select, update, delete, inserttableName is the table name (or null if not determinable)columnName is the column nameSpecial Cases:
SELECT *, DELETE, and INSERT INTO table VALUES() without specified columns, the .* regex pattern is usedUsage Examples:
// Specific column access
const columns1 = parser.columnList('SELECT name, email FROM users');
console.log(columns1); // ["select::users::name", "select::users::email"]
// Qualified column names
const columns2 = parser.columnList('SELECT u.name, p.title FROM users u JOIN posts p ON u.id = p.user_id');
console.log(columns2);
// ["select::u::name", "select::p::title", "select::u::id", "select::p::user_id"]
// WHERE clause columns
const columns3 = parser.columnList('SELECT name FROM users WHERE age > 18 AND status = "active"');
console.log(columns3);
// ["select::users::name", "select::users::age", "select::users::status"]
// UPDATE operations
const columns4 = parser.columnList('UPDATE users SET email = "new@email.com" WHERE id = 1');
console.log(columns4); // ["update::users::email", "update::users::id"]
// Wildcard selections (require .* authority)
const columns5 = parser.columnList('SELECT * FROM users');
console.log(columns5); // ["select::users::.*"]
// Complex expressions
const columns6 = parser.columnList('SELECT COUNT(id), MAX(created_at) FROM orders GROUP BY user_id');
console.log(columns6);
// ["select::orders::id", "select::orders::created_at", "select::orders::user_id"]Check SQL queries against whitelist patterns to enforce access control policies.
/**
* Check SQL against whitelist authority patterns
* @param sql - SQL string to check
* @param whiteList - Array of allowed patterns (regex strings)
* @param opt - Optional configuration object
* @returns Error if check fails, undefined if passes
* @throws Error with detailed message if unauthorized access detected
*/
whiteListCheck(sql: string, whiteList: string[], opt?: Option): Error | undefined;
type WhilteListCheckMode = "table" | "column";
interface Option {
database?: string;
type?: WhilteListCheckMode; // Default: "table"
}Usage Examples:
// Table whitelist checking (default mode)
const tableWhitelist = [
'select::(.*)::(users|posts)', // Allow SELECT on users and posts tables
'update::(.*)::users', // Allow UPDATE on users table
'insert::(.*)::posts' // Allow INSERT on posts table
];
try {
// This passes - SELECT on users is allowed
parser.whiteListCheck(
'SELECT * FROM users',
tableWhitelist
);
// This fails - DELETE is not in whitelist
parser.whiteListCheck(
'DELETE FROM users WHERE id = 1',
tableWhitelist
);
} catch (error) {
console.error('Access denied:', error.message);
// "authority = 'delete::null::users' is required in table whiteList to execute SQL = 'DELETE FROM users WHERE id = 1'"
}
// Column whitelist checking
const columnWhitelist = [
'select::users::(name|email)', // Allow SELECT on name and email columns
'select::users::.*', // Allow wildcard SELECT on users
'update::users::email' // Allow UPDATE on email column only
];
try {
// This passes - accessing allowed columns
parser.whiteListCheck(
'SELECT name, email FROM users',
columnWhitelist,
{ type: 'column' }
);
// This fails - password column not allowed
parser.whiteListCheck(
'SELECT name, password FROM users',
columnWhitelist,
{ type: 'column' }
);
} catch (error) {
console.error('Column access denied:', error.message);
}
// Database-specific whitelist
const pgWhitelist = ['select::public::(users|posts)'];
parser.whiteListCheck(
'SELECT * FROM public.users',
pgWhitelist,
{ database: 'PostgreSQL', type: 'table' }
);
// Complex whitelist patterns
const complexWhitelist = [
'^(select|insert)::(.*)::(user_.*|post_.*)$', // Allow select/insert on tables starting with user_ or post_
'^update::(.*)::(user_profile|user_settings)$' // Allow update only on specific user tables
];Whitelist patterns use regular expressions for flexible access control:
// Exact match
'select::null::users' // Exact table match
// Wildcard database
'select::(.*)::users' // Any database, users table
// Multiple tables
'select::(.*)::(users|posts|comments)' // Multiple specific tables
// Table pattern matching
'^(select|insert)::(.*)::(user_.*)$' // Tables starting with user_
// Column patterns
'select::users::(name|email|created_at)' // Specific columns only
'select::users::.*' // All columns (wildcard)
'^select::(.*)::(?!password).*$' // All columns except password
// Mixed patterns
'(select|update)::(.*)::(users|profiles)' // Multiple operations and tablesCommon Whitelist Scenarios:
// Read-only access to specific tables
const readOnlyWhitelist = [
'select::(.*)::(users|posts|comments)',
'select::(.*)::.*' // Allow column wildcards
];
// Limited user management
const userMgmtWhitelist = [
'select::(.*)::(users|user_profiles)',
'update::(.*)::user_profiles',
'insert::(.*)::user_profiles'
];
// Reporting access (read-only, specific columns)
const reportingWhitelist = [
'select::users::(id|name|email|created_at)',
'select::orders::(id|user_id|total|status|created_at)',
'select::products::(id|name|price|category)'
];
// Admin access (most operations allowed)
const adminWhitelist = [
'(select|insert|update)::(.*)::((?!sensitive_data).)*', // All except sensitive_data table
'select::(.*)::((?!password|ssn|credit_card).)*' // All columns except sensitive ones
];Always validate SQL queries before processing:
function validateAndExecute(sql, whitelist) {
try {
// First check syntax by parsing
const ast = parser.astify(sql);
// Then check authorization
parser.whiteListCheck(sql, whitelist);
// Safe to execute
return executeQuery(sql);
} catch (error) {
if (error.message.includes('authority')) {
throw new Error('Access denied: ' + error.message);
} else {
throw new Error('Invalid SQL: ' + error.message);
}
}
}Analyze all aspects of a query:
function analyzeQuery(sql) {
const tables = parser.tableList(sql);
const columns = parser.columnList(sql);
const ast = parser.astify(sql);
return {
tables,
columns,
operations: [...new Set(tables.map(t => t.split('::')[0]))],
databases: [...new Set(tables.map(t => t.split('::')[1]).filter(d => d !== 'null'))],
ast
};
}
const analysis = analyzeQuery('UPDATE users SET email = "new@email.com" WHERE id = 1');
console.log(analysis);
// {
// tables: ["update::null::users"],
// columns: ["update::users::email", "update::users::id"],
// operations: ["update"],
// databases: [],
// ast: { ... }
// }Implement layered security checks:
function checkMultiLevelAuth(sql, userRole) {
const baseWhitelist = ['select::(.*)::(users|posts)']; // Basic read access
const adminWhitelist = [...baseWhitelist, 'update::(.*)::(.*)', 'delete::(.*)::(.*)']; // Admin access
const whitelist = userRole === 'admin' ? adminWhitelist : baseWhitelist;
// Check table access
parser.whiteListCheck(sql, whitelist, { type: 'table' });
// Additional column-level check for sensitive operations
if (sql.toLowerCase().includes('update') || sql.toLowerCase().includes('insert')) {
const columnWhitelist = userRole === 'admin'
? ['(update|insert)::(.*)::((?!password|ssn).)*']
: ['insert::(.*)::(name|email)'];
parser.whiteListCheck(sql, columnWhitelist, { type: 'column' });
}
}