-
Notifications
You must be signed in to change notification settings - Fork 189
Expand file tree
/
Copy pathsql-validator.ts
More file actions
86 lines (77 loc) · 2.03 KB
/
Copy pathsql-validator.ts
File metadata and controls
86 lines (77 loc) · 2.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const FORBIDDEN_SQL_KEYWORDS = [
'INSERT INTO',
'UPDATE SET',
'DELETE FROM',
'DROP TABLE',
'DROP DATABASE',
'CREATE TABLE',
'CREATE DATABASE',
'ALTER TABLE',
'EXEC ',
'EXECUTE ',
'TRUNCATE',
'MERGE',
'BULK',
'RESTORE',
'BACKUP',
'GRANT',
'REVOKE',
'SHOW GRANTS',
'SHOW USERS',
'SYSTEM',
'ATTACH',
'DETACH',
'OPTIMIZE',
'CHECK',
'REPAIR',
'ANALYZE',
] as const;
const DANGEROUS_PATTERNS = [
/--/, // SQL comments
/\/\*/, // Multi-line comments start
/\*\//, // Multi-line comments end
/;\s*(DROP|DELETE|UPDATE|INSERT|CREATE|ALTER|TRUNCATE)/i, // Suspicious stacked queries
/\bINTO\s+OUTFILE\b/i, // File operations
/\bLOAD_FILE\b/i, // File reading
/\bINTO\s+DUMPFILE\b/i, // File writing
/\bSHOW\s+PROCESSLIST\b/i, // Process information
/\bINFORMATION_SCHEMA\b/i, // Schema inspection
/\bMYSQL\b/i, // MySQL database access
/\bPG_/i, // PostgreSQL functions
/\bUNION\s+(ALL\s+)?SELECT\b/i, // Union-based injection attempts
/\bOR\s+[\d'"]+=[\d'"]+/i, // Classic SQL injection patterns like OR 1=1
/\bAND\s+[\d'"]+=[\d'"]+/i, // Classic SQL injection patterns like AND 1=1
] as const;
export function validateSQL(sql: string): boolean {
if (!sql || typeof sql !== 'string') {
return false;
}
const upperSQL = sql.toUpperCase();
const trimmed = upperSQL.trim();
// Check length limit to prevent resource exhaustion
if (sql.length > 10000) {
return false;
}
// Check for dangerous keyword patterns
for (const keyword of FORBIDDEN_SQL_KEYWORDS) {
if (upperSQL.includes(keyword)) {
return false;
}
}
// Check for dangerous patterns
for (const pattern of DANGEROUS_PATTERNS) {
if (pattern.test(sql)) {
return false;
}
}
// Must start with SELECT or WITH (for CTEs)
if (!trimmed.startsWith('SELECT') && !trimmed.startsWith('WITH')) {
return false;
}
// Additional validation: Ensure no stacked queries
const statements = sql.split(';').filter(s => s.trim());
if (statements.length > 1) {
return false;
}
return true;
}