/** * SQL Validator * * Shared validation for read-only SQL queries (widgets, admin SQL executor). * Ensures queries are SELECT-only and don't contain dangerous patterns. */ const DEFAULT_MAX_LENGTH = 5000; const RESTRICTED_FUNCTIONS = /\b(pg_sleep|set_config|copy)\b/i; /** * Validate a SQL query for read-only execution * @param {string} sql - The SQL query to validate * @param {object} options - Validation options * @param {number} [options.maxLength=5000] - Maximum allowed query length * @returns {{ valid: boolean, error?: string, normalized?: string }} */ function validateReadOnlySql(sql, options = {}) { const maxLength = options.maxLength || DEFAULT_MAX_LENGTH; if (typeof sql !== 'string' || !sql.trim()) { return { valid: false, error: 'SQL query must be a non-empty string' }; } if (sql.length > maxLength) { return { valid: false, error: `SQL query is too long (max ${maxLength} characters)`, }; } const normalized = sql.trim().replace(/;+\s*$/, ''); if (!/^(select|with)\b/i.test(normalized)) { return { valid: false, error: 'Only SELECT statements are allowed' }; } if (normalized.includes(';')) { return { valid: false, error: 'Only a single statement is allowed' }; } if (/--|\/\*/.test(normalized)) { return { valid: false, error: 'SQL comments are not allowed' }; } if (RESTRICTED_FUNCTIONS.test(normalized)) { return { valid: false, error: 'Restricted SQL function detected' }; } return { valid: true, normalized }; } module.exports = { validateReadOnlySql };