@freshguard/freshguard-core 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +644 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +350 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/connectors/base-connector.d.ts +62 -0
- package/dist/connectors/base-connector.d.ts.map +1 -0
- package/dist/connectors/base-connector.js +549 -0
- package/dist/connectors/base-connector.js.map +1 -0
- package/dist/connectors/bigquery.d.ts +38 -0
- package/dist/connectors/bigquery.d.ts.map +1 -0
- package/dist/connectors/bigquery.js +406 -0
- package/dist/connectors/bigquery.js.map +1 -0
- package/dist/connectors/duckdb.d.ts +36 -0
- package/dist/connectors/duckdb.d.ts.map +1 -0
- package/dist/connectors/duckdb.js +364 -0
- package/dist/connectors/duckdb.js.map +1 -0
- package/dist/connectors/index.d.ts +7 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +7 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/mysql.d.ts +32 -0
- package/dist/connectors/mysql.d.ts.map +1 -0
- package/dist/connectors/mysql.js +348 -0
- package/dist/connectors/mysql.js.map +1 -0
- package/dist/connectors/postgres.d.ts +31 -0
- package/dist/connectors/postgres.d.ts.map +1 -0
- package/dist/connectors/postgres.js +326 -0
- package/dist/connectors/postgres.js.map +1 -0
- package/dist/connectors/redshift.d.ts +32 -0
- package/dist/connectors/redshift.d.ts.map +1 -0
- package/dist/connectors/redshift.js +366 -0
- package/dist/connectors/redshift.js.map +1 -0
- package/dist/connectors/snowflake.d.ts +43 -0
- package/dist/connectors/snowflake.d.ts.map +1 -0
- package/dist/connectors/snowflake.js +442 -0
- package/dist/connectors/snowflake.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +10 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +12 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +114 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema.d.ts +2053 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +164 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/errors/debug-factory.d.ts +23 -0
- package/dist/errors/debug-factory.d.ts.map +1 -0
- package/dist/errors/debug-factory.js +149 -0
- package/dist/errors/debug-factory.js.map +1 -0
- package/dist/errors/index.d.ts +119 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +341 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata/duckdb-storage.d.ts +31 -0
- package/dist/metadata/duckdb-storage.d.ts.map +1 -0
- package/dist/metadata/duckdb-storage.js +230 -0
- package/dist/metadata/duckdb-storage.js.map +1 -0
- package/dist/metadata/factory.d.ts +4 -0
- package/dist/metadata/factory.d.ts.map +1 -0
- package/dist/metadata/factory.js +23 -0
- package/dist/metadata/factory.js.map +1 -0
- package/dist/metadata/index.d.ts +6 -0
- package/dist/metadata/index.d.ts.map +1 -0
- package/dist/metadata/index.js +4 -0
- package/dist/metadata/index.js.map +1 -0
- package/dist/metadata/interface.d.ts +26 -0
- package/dist/metadata/interface.d.ts.map +1 -0
- package/dist/metadata/interface.js +2 -0
- package/dist/metadata/interface.js.map +1 -0
- package/dist/metadata/postgresql-storage.d.ts +32 -0
- package/dist/metadata/postgresql-storage.d.ts.map +1 -0
- package/dist/metadata/postgresql-storage.js +242 -0
- package/dist/metadata/postgresql-storage.js.map +1 -0
- package/dist/metadata/schema-config.d.ts +30 -0
- package/dist/metadata/schema-config.d.ts.map +1 -0
- package/dist/metadata/schema-config.js +94 -0
- package/dist/metadata/schema-config.js.map +1 -0
- package/dist/metadata/types.d.ts +35 -0
- package/dist/metadata/types.d.ts.map +1 -0
- package/dist/metadata/types.js +2 -0
- package/dist/metadata/types.js.map +1 -0
- package/dist/monitor/baseline-calculator.d.ts +30 -0
- package/dist/monitor/baseline-calculator.d.ts.map +1 -0
- package/dist/monitor/baseline-calculator.js +192 -0
- package/dist/monitor/baseline-calculator.js.map +1 -0
- package/dist/monitor/baseline-config.d.ts +37 -0
- package/dist/monitor/baseline-config.d.ts.map +1 -0
- package/dist/monitor/baseline-config.js +156 -0
- package/dist/monitor/baseline-config.js.map +1 -0
- package/dist/monitor/freshness.d.ts +5 -0
- package/dist/monitor/freshness.d.ts.map +1 -0
- package/dist/monitor/freshness.js +239 -0
- package/dist/monitor/freshness.js.map +1 -0
- package/dist/monitor/index.d.ts +5 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/monitor/index.js +5 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/monitor/schema-baseline.d.ts +22 -0
- package/dist/monitor/schema-baseline.d.ts.map +1 -0
- package/dist/monitor/schema-baseline.js +211 -0
- package/dist/monitor/schema-baseline.js.map +1 -0
- package/dist/monitor/schema-changes.d.ts +5 -0
- package/dist/monitor/schema-changes.d.ts.map +1 -0
- package/dist/monitor/schema-changes.js +289 -0
- package/dist/monitor/schema-changes.js.map +1 -0
- package/dist/monitor/volume.d.ts +5 -0
- package/dist/monitor/volume.d.ts.map +1 -0
- package/dist/monitor/volume.js +262 -0
- package/dist/monitor/volume.js.map +1 -0
- package/dist/observability/logger.d.ts +63 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +282 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/metrics.d.ts +106 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +441 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/query-analyzer.js +526 -0
- package/dist/resilience/circuit-breaker.d.ts +94 -0
- package/dist/resilience/circuit-breaker.d.ts.map +1 -0
- package/dist/resilience/circuit-breaker.js +379 -0
- package/dist/resilience/circuit-breaker.js.map +1 -0
- package/dist/resilience/index.d.ts +7 -0
- package/dist/resilience/index.d.ts.map +1 -0
- package/dist/resilience/index.js +7 -0
- package/dist/resilience/index.js.map +1 -0
- package/dist/resilience/retry-policy.d.ts +87 -0
- package/dist/resilience/retry-policy.d.ts.map +1 -0
- package/dist/resilience/retry-policy.js +423 -0
- package/dist/resilience/retry-policy.js.map +1 -0
- package/dist/resilience/timeout-manager.d.ts +97 -0
- package/dist/resilience/timeout-manager.d.ts.map +1 -0
- package/dist/resilience/timeout-manager.js +339 -0
- package/dist/resilience/timeout-manager.js.map +1 -0
- package/dist/security/query-analyzer.d.ts +82 -0
- package/dist/security/query-analyzer.d.ts.map +1 -0
- package/dist/security/query-analyzer.js +381 -0
- package/dist/security/query-analyzer.js.map +1 -0
- package/dist/security/schema-cache.d.ts +95 -0
- package/dist/security/schema-cache.d.ts.map +1 -0
- package/dist/security/schema-cache.js +344 -0
- package/dist/security/schema-cache.js.map +1 -0
- package/dist/types/connector.d.ts +68 -0
- package/dist/types/connector.d.ts.map +1 -0
- package/dist/types/connector.js +26 -0
- package/dist/types/connector.js.map +1 -0
- package/dist/types.d.ts +244 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation/index.d.ts +7 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +5 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/runtime-validator.d.ts +70 -0
- package/dist/validation/runtime-validator.d.ts.map +1 -0
- package/dist/validation/runtime-validator.js +206 -0
- package/dist/validation/runtime-validator.js.map +1 -0
- package/dist/validation/sanitizers.d.ts +56 -0
- package/dist/validation/sanitizers.d.ts.map +1 -0
- package/dist/validation/sanitizers.js +264 -0
- package/dist/validation/sanitizers.js.map +1 -0
- package/dist/validation/schemas.d.ts +224 -0
- package/dist/validation/schemas.d.ts.map +1 -0
- package/dist/validation/schemas.js +263 -0
- package/dist/validation/schemas.js.map +1 -0
- package/dist/validators/index.d.ts +18 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +209 -0
- package/dist/validators/index.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Query Complexity Analyzer for FreshGuard Core Phase 2
|
|
4
|
+
*
|
|
5
|
+
* Analyzes SQL queries for complexity and security risks, providing
|
|
6
|
+
* risk scoring and recommendations for safe query execution.
|
|
7
|
+
*
|
|
8
|
+
* @license MIT
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.defaultQueryAnalyzer = exports.QueryComplexityAnalyzer = void 0;
|
|
12
|
+
exports.createQueryAnalyzer = createQueryAnalyzer;
|
|
13
|
+
exports.createSecurityAnalyzer = createSecurityAnalyzer;
|
|
14
|
+
exports.createPerformanceAnalyzer = createPerformanceAnalyzer;
|
|
15
|
+
// ==============================================
|
|
16
|
+
// Default Configuration
|
|
17
|
+
// ==============================================
|
|
18
|
+
/**
|
|
19
|
+
* Default query analyzer configuration
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_CONFIG = {
|
|
22
|
+
maxRiskScore: 70,
|
|
23
|
+
maxComplexityScore: 80,
|
|
24
|
+
maxEstimatedCost: 1000000, // 1 million cost units
|
|
25
|
+
maxResultSetSize: 10000,
|
|
26
|
+
enablePerformanceAnalysis: true,
|
|
27
|
+
enableSecurityAnalysis: true,
|
|
28
|
+
customRiskFactors: []
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Default risk factors for security analysis
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_RISK_FACTORS = [
|
|
34
|
+
{
|
|
35
|
+
pattern: /union\s+all|union\s+select/i,
|
|
36
|
+
riskScore: 30,
|
|
37
|
+
description: 'UNION operations can be expensive and may indicate injection attempts',
|
|
38
|
+
blocking: false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: /\bor\s+1\s*=\s*1\b/i,
|
|
42
|
+
riskScore: 90,
|
|
43
|
+
description: 'Classic SQL injection pattern detected',
|
|
44
|
+
blocking: true
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: /\bor\s+\'.*?\'\s*=\s*\'.*?\'/i,
|
|
48
|
+
riskScore: 85,
|
|
49
|
+
description: 'Potential SQL injection with string comparison',
|
|
50
|
+
blocking: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /;\s*drop\s+table/i,
|
|
54
|
+
riskScore: 100,
|
|
55
|
+
description: 'SQL injection attempt to drop table',
|
|
56
|
+
blocking: true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
pattern: /;\s*delete\s+from/i,
|
|
60
|
+
riskScore: 100,
|
|
61
|
+
description: 'SQL injection attempt to delete data',
|
|
62
|
+
blocking: true
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /select\s+\*\s+from\s+information_schema/i,
|
|
66
|
+
riskScore: 40,
|
|
67
|
+
description: 'Information schema access - potentially sensitive',
|
|
68
|
+
blocking: false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /select\s+.*\s+from\s+.*\s+where\s+1\s*=\s*1/i,
|
|
72
|
+
riskScore: 70,
|
|
73
|
+
description: 'Suspicious WHERE clause that always evaluates to true',
|
|
74
|
+
blocking: false
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
pattern: /\/\*.*?\*\//,
|
|
78
|
+
riskScore: 20,
|
|
79
|
+
description: 'SQL comments detected - review for injection attempts',
|
|
80
|
+
blocking: false
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
pattern: /--.*$/m,
|
|
84
|
+
riskScore: 25,
|
|
85
|
+
description: 'SQL line comments detected - review for injection attempts',
|
|
86
|
+
blocking: false
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
// ==============================================
|
|
90
|
+
// SQL Parser Utilities
|
|
91
|
+
// ==============================================
|
|
92
|
+
/**
|
|
93
|
+
* Simple SQL parser for query analysis
|
|
94
|
+
* Note: This is a simplified parser for basic analysis
|
|
95
|
+
*/
|
|
96
|
+
class SimpleSQLParser {
|
|
97
|
+
/**
|
|
98
|
+
* Parse basic query structure
|
|
99
|
+
*/
|
|
100
|
+
static parseQuery(sql) {
|
|
101
|
+
const normalizedSql = sql.trim().toLowerCase();
|
|
102
|
+
// Determine query type
|
|
103
|
+
const queryType = this.getQueryType(normalizedSql);
|
|
104
|
+
// Count tables (simplified - counts FROM and JOIN clauses)
|
|
105
|
+
const tableCount = this.countTables(normalizedSql);
|
|
106
|
+
// Count joins
|
|
107
|
+
const joinCount = this.countJoins(normalizedSql);
|
|
108
|
+
// Check for various SQL constructs
|
|
109
|
+
const hasSubqueries = /\(\s*select\b/.test(normalizedSql);
|
|
110
|
+
const hasAggregations = /\b(count|sum|avg|max|min|group_concat)\s*\(/.test(normalizedSql);
|
|
111
|
+
const hasWildcards = /select\s+\*\s+from\b/.test(normalizedSql);
|
|
112
|
+
const hasWhere = /\bwhere\b/.test(normalizedSql);
|
|
113
|
+
const hasOrderBy = /\border\s+by\b/.test(normalizedSql);
|
|
114
|
+
const hasGroupBy = /\bgroup\s+by\b/.test(normalizedSql);
|
|
115
|
+
const hasHaving = /\bhaving\b/.test(normalizedSql);
|
|
116
|
+
// Check for LIMIT
|
|
117
|
+
const limitMatch = normalizedSql.match(/\blimit\s+(\d+)/);
|
|
118
|
+
const hasLimit = !!limitMatch;
|
|
119
|
+
const limitValue = limitMatch ? parseInt(limitMatch[1], 10) : undefined;
|
|
120
|
+
// Estimate result set size (simplified)
|
|
121
|
+
const estimatedResultSize = this.estimateResultSize(tableCount, hasWhere, hasLimit, limitValue);
|
|
122
|
+
return {
|
|
123
|
+
queryType,
|
|
124
|
+
tableCount,
|
|
125
|
+
joinCount,
|
|
126
|
+
hasSubqueries,
|
|
127
|
+
hasAggregations,
|
|
128
|
+
hasWildcards,
|
|
129
|
+
hasLimit,
|
|
130
|
+
limitValue,
|
|
131
|
+
hasWhere,
|
|
132
|
+
hasOrderBy,
|
|
133
|
+
hasGroupBy,
|
|
134
|
+
hasHaving,
|
|
135
|
+
estimatedResultSize
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get query type from SQL
|
|
140
|
+
*/
|
|
141
|
+
static getQueryType(sql) {
|
|
142
|
+
if (sql.startsWith('select'))
|
|
143
|
+
return 'SELECT';
|
|
144
|
+
if (sql.startsWith('insert'))
|
|
145
|
+
return 'INSERT';
|
|
146
|
+
if (sql.startsWith('update'))
|
|
147
|
+
return 'UPDATE';
|
|
148
|
+
if (sql.startsWith('delete'))
|
|
149
|
+
return 'DELETE';
|
|
150
|
+
if (sql.startsWith('create'))
|
|
151
|
+
return 'CREATE';
|
|
152
|
+
if (sql.startsWith('drop'))
|
|
153
|
+
return 'DROP';
|
|
154
|
+
if (sql.startsWith('alter'))
|
|
155
|
+
return 'ALTER';
|
|
156
|
+
if (sql.startsWith('show'))
|
|
157
|
+
return 'SHOW';
|
|
158
|
+
if (sql.startsWith('describe') || sql.startsWith('desc'))
|
|
159
|
+
return 'DESCRIBE';
|
|
160
|
+
return 'UNKNOWN';
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Count number of tables in query
|
|
164
|
+
*/
|
|
165
|
+
static countTables(sql) {
|
|
166
|
+
// Count FROM clauses
|
|
167
|
+
const fromMatches = sql.match(/\bfrom\s+[\w\.]+/g) || [];
|
|
168
|
+
// Count JOIN clauses
|
|
169
|
+
const joinMatches = sql.match(/\bjoin\s+[\w\.]+/g) || [];
|
|
170
|
+
return fromMatches.length + joinMatches.length;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Count number of joins
|
|
174
|
+
*/
|
|
175
|
+
static countJoins(sql) {
|
|
176
|
+
const joinMatches = sql.match(/\b(inner\s+join|left\s+join|right\s+join|full\s+join|join)\b/g) || [];
|
|
177
|
+
return joinMatches.length;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Estimate result set size
|
|
181
|
+
*/
|
|
182
|
+
static estimateResultSize(tableCount, hasWhere, hasLimit, limitValue) {
|
|
183
|
+
// If LIMIT is specified, use that as max
|
|
184
|
+
if (hasLimit && limitValue) {
|
|
185
|
+
return Math.min(limitValue, 10000);
|
|
186
|
+
}
|
|
187
|
+
// Base estimate on table count and filtering
|
|
188
|
+
let estimate = Math.pow(1000, tableCount); // Exponential growth with joins
|
|
189
|
+
// Reduce estimate if WHERE clause exists (assumes filtering)
|
|
190
|
+
if (hasWhere) {
|
|
191
|
+
estimate = Math.floor(estimate * 0.1); // Assume WHERE reduces by 90%
|
|
192
|
+
}
|
|
193
|
+
// Cap at reasonable maximum
|
|
194
|
+
return Math.min(estimate, 100000);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ==============================================
|
|
198
|
+
// Query Complexity Analyzer
|
|
199
|
+
// ==============================================
|
|
200
|
+
/**
|
|
201
|
+
* Analyzes SQL queries for complexity and security risks
|
|
202
|
+
*/
|
|
203
|
+
class QueryComplexityAnalyzer {
|
|
204
|
+
constructor(config = {}) {
|
|
205
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
206
|
+
this.riskFactors = [
|
|
207
|
+
...DEFAULT_RISK_FACTORS,
|
|
208
|
+
...(config.customRiskFactors || [])
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Analyze a SQL query for complexity and security risks
|
|
213
|
+
*/
|
|
214
|
+
analyzeQuery(sql, tableMetadata = []) {
|
|
215
|
+
// Parse query structure
|
|
216
|
+
const details = SimpleSQLParser.parseQuery(sql);
|
|
217
|
+
// Calculate scores
|
|
218
|
+
const complexityScore = this.calculateComplexityScore(details, tableMetadata);
|
|
219
|
+
const riskScore = this.calculateRiskScore(sql, details);
|
|
220
|
+
const estimatedCost = this.calculateEstimatedCost(details, tableMetadata);
|
|
221
|
+
// Generate warnings and recommendations
|
|
222
|
+
const securityWarnings = this.generateSecurityWarnings(sql, details);
|
|
223
|
+
const performanceWarnings = this.generatePerformanceWarnings(details, tableMetadata);
|
|
224
|
+
const recommendations = this.generateRecommendations(details, securityWarnings, performanceWarnings);
|
|
225
|
+
// Determine if execution should be allowed
|
|
226
|
+
const allowExecution = this.shouldAllowExecution(sql, riskScore, complexityScore, estimatedCost, details);
|
|
227
|
+
return {
|
|
228
|
+
allowExecution,
|
|
229
|
+
riskScore,
|
|
230
|
+
complexityScore,
|
|
231
|
+
estimatedCost,
|
|
232
|
+
securityWarnings,
|
|
233
|
+
performanceWarnings,
|
|
234
|
+
recommendations,
|
|
235
|
+
details
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Calculate query complexity score (0-100)
|
|
240
|
+
*/
|
|
241
|
+
calculateComplexityScore(details, tableMetadata) {
|
|
242
|
+
let score = 0;
|
|
243
|
+
// Base score by query type
|
|
244
|
+
switch (details.queryType) {
|
|
245
|
+
case 'SELECT':
|
|
246
|
+
score += 5;
|
|
247
|
+
break;
|
|
248
|
+
case 'INSERT':
|
|
249
|
+
score += 15;
|
|
250
|
+
break;
|
|
251
|
+
case 'UPDATE':
|
|
252
|
+
score += 20;
|
|
253
|
+
break;
|
|
254
|
+
case 'DELETE':
|
|
255
|
+
score += 25;
|
|
256
|
+
break;
|
|
257
|
+
case 'CREATE':
|
|
258
|
+
score += 30;
|
|
259
|
+
break;
|
|
260
|
+
case 'DROP':
|
|
261
|
+
score += 50;
|
|
262
|
+
break;
|
|
263
|
+
case 'ALTER':
|
|
264
|
+
score += 40;
|
|
265
|
+
break;
|
|
266
|
+
default: score += 10;
|
|
267
|
+
}
|
|
268
|
+
// Table complexity
|
|
269
|
+
score += Math.min(details.tableCount * 10, 30); // Max 30 for tables
|
|
270
|
+
// Join complexity
|
|
271
|
+
score += Math.min(details.joinCount * 15, 40); // Max 40 for joins
|
|
272
|
+
// Subquery complexity
|
|
273
|
+
if (details.hasSubqueries)
|
|
274
|
+
score += 20;
|
|
275
|
+
// Aggregation complexity
|
|
276
|
+
if (details.hasAggregations)
|
|
277
|
+
score += 10;
|
|
278
|
+
// Wildcard penalty (SELECT *)
|
|
279
|
+
if (details.hasWildcards)
|
|
280
|
+
score += 15;
|
|
281
|
+
// Missing WHERE clause on multi-table queries
|
|
282
|
+
if (details.tableCount > 1 && !details.hasWhere)
|
|
283
|
+
score += 25;
|
|
284
|
+
// Large result set penalty
|
|
285
|
+
if (details.estimatedResultSize > 1000)
|
|
286
|
+
score += 10;
|
|
287
|
+
if (details.estimatedResultSize > 10000)
|
|
288
|
+
score += 20;
|
|
289
|
+
// No LIMIT on potentially large results
|
|
290
|
+
if (!details.hasLimit && details.estimatedResultSize > 1000)
|
|
291
|
+
score += 15;
|
|
292
|
+
return Math.min(score, 100);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Calculate security risk score (0-100)
|
|
296
|
+
*/
|
|
297
|
+
calculateRiskScore(sql, details) {
|
|
298
|
+
let score = 0;
|
|
299
|
+
if (!this.config.enableSecurityAnalysis) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
// Check against risk factors
|
|
303
|
+
for (const factor of this.riskFactors) {
|
|
304
|
+
if (factor.pattern.test(sql)) {
|
|
305
|
+
score += factor.riskScore;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Additional risk factors based on query structure
|
|
309
|
+
if (details.queryType !== 'SELECT' && details.queryType !== 'SHOW' && details.queryType !== 'DESCRIBE') {
|
|
310
|
+
score += 30; // Non-read operations are inherently riskier
|
|
311
|
+
}
|
|
312
|
+
// Multiple statements (potential injection)
|
|
313
|
+
const statementCount = sql.split(';').filter(s => s.trim()).length;
|
|
314
|
+
if (statementCount > 1) {
|
|
315
|
+
score += 40;
|
|
316
|
+
}
|
|
317
|
+
return Math.min(score, 100);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Calculate estimated execution cost
|
|
321
|
+
*/
|
|
322
|
+
calculateEstimatedCost(details, tableMetadata) {
|
|
323
|
+
let cost = 1; // Base cost
|
|
324
|
+
// Cost based on estimated result size
|
|
325
|
+
cost += details.estimatedResultSize * 0.1;
|
|
326
|
+
// Join costs (exponential)
|
|
327
|
+
if (details.joinCount > 0) {
|
|
328
|
+
cost *= Math.pow(10, details.joinCount);
|
|
329
|
+
}
|
|
330
|
+
// Table scan costs
|
|
331
|
+
for (let i = 0; i < details.tableCount; i++) {
|
|
332
|
+
const metadata = tableMetadata[i];
|
|
333
|
+
if (metadata) {
|
|
334
|
+
cost += metadata.estimatedRows * 0.01;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
cost += 10000; // Unknown table - assume large
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Subquery costs
|
|
341
|
+
if (details.hasSubqueries) {
|
|
342
|
+
cost *= 5;
|
|
343
|
+
}
|
|
344
|
+
// Aggregation costs
|
|
345
|
+
if (details.hasAggregations) {
|
|
346
|
+
cost *= 2;
|
|
347
|
+
}
|
|
348
|
+
// Sorting costs
|
|
349
|
+
if (details.hasOrderBy && !details.hasLimit) {
|
|
350
|
+
cost *= 3;
|
|
351
|
+
}
|
|
352
|
+
return Math.floor(cost);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Generate security warnings
|
|
356
|
+
*/
|
|
357
|
+
generateSecurityWarnings(sql, details) {
|
|
358
|
+
const warnings = [];
|
|
359
|
+
if (!this.config.enableSecurityAnalysis) {
|
|
360
|
+
return warnings;
|
|
361
|
+
}
|
|
362
|
+
// Check risk factors
|
|
363
|
+
for (const factor of this.riskFactors) {
|
|
364
|
+
if (factor.pattern.test(sql)) {
|
|
365
|
+
warnings.push(factor.description);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Additional security checks
|
|
369
|
+
if (details.queryType !== 'SELECT' && details.queryType !== 'SHOW' && details.queryType !== 'DESCRIBE') {
|
|
370
|
+
warnings.push('Non-read operation detected - ensure proper authorization');
|
|
371
|
+
}
|
|
372
|
+
if (sql.includes(';') && sql.split(';').filter(s => s.trim()).length > 1) {
|
|
373
|
+
warnings.push('Multiple SQL statements detected - potential injection risk');
|
|
374
|
+
}
|
|
375
|
+
if (details.hasWildcards && details.tableCount > 0) {
|
|
376
|
+
warnings.push('SELECT * detected - may expose sensitive columns');
|
|
377
|
+
}
|
|
378
|
+
return warnings;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Generate performance warnings
|
|
382
|
+
*/
|
|
383
|
+
generatePerformanceWarnings(details, tableMetadata) {
|
|
384
|
+
const warnings = [];
|
|
385
|
+
if (!this.config.enablePerformanceAnalysis) {
|
|
386
|
+
return warnings;
|
|
387
|
+
}
|
|
388
|
+
// Large result set without LIMIT
|
|
389
|
+
if (details.estimatedResultSize > 1000 && !details.hasLimit) {
|
|
390
|
+
warnings.push(`Large result set estimated (${details.estimatedResultSize}) without LIMIT clause`);
|
|
391
|
+
}
|
|
392
|
+
// Multiple joins without WHERE
|
|
393
|
+
if (details.joinCount > 1 && !details.hasWhere) {
|
|
394
|
+
warnings.push('Multiple JOINs without WHERE clause may produce Cartesian product');
|
|
395
|
+
}
|
|
396
|
+
// SELECT * on large tables
|
|
397
|
+
if (details.hasWildcards && tableMetadata.some(t => t.estimatedRows > 10000)) {
|
|
398
|
+
warnings.push('SELECT * on large table(s) - consider selecting specific columns');
|
|
399
|
+
}
|
|
400
|
+
// ORDER BY without LIMIT on large result
|
|
401
|
+
if (details.hasOrderBy && !details.hasLimit && details.estimatedResultSize > 1000) {
|
|
402
|
+
warnings.push('ORDER BY without LIMIT on large result set - consider adding LIMIT');
|
|
403
|
+
}
|
|
404
|
+
// Subqueries
|
|
405
|
+
if (details.hasSubqueries) {
|
|
406
|
+
warnings.push('Subqueries detected - consider using JOINs for better performance');
|
|
407
|
+
}
|
|
408
|
+
return warnings;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Generate optimization recommendations
|
|
412
|
+
*/
|
|
413
|
+
generateRecommendations(details, securityWarnings, performanceWarnings) {
|
|
414
|
+
const recommendations = [];
|
|
415
|
+
// Security recommendations
|
|
416
|
+
if (securityWarnings.length > 0) {
|
|
417
|
+
recommendations.push('Review security warnings and validate query source');
|
|
418
|
+
}
|
|
419
|
+
if (details.hasWildcards) {
|
|
420
|
+
recommendations.push('Replace SELECT * with specific column names');
|
|
421
|
+
}
|
|
422
|
+
// Performance recommendations
|
|
423
|
+
if (!details.hasLimit && details.estimatedResultSize > 1000) {
|
|
424
|
+
recommendations.push('Add LIMIT clause to prevent large result sets');
|
|
425
|
+
}
|
|
426
|
+
if (details.joinCount > 0 && !details.hasWhere) {
|
|
427
|
+
recommendations.push('Add WHERE clause to filter results and improve performance');
|
|
428
|
+
}
|
|
429
|
+
if (details.hasOrderBy && details.estimatedResultSize > 1000) {
|
|
430
|
+
recommendations.push('Consider adding indexes on ORDER BY columns');
|
|
431
|
+
}
|
|
432
|
+
if (details.hasSubqueries) {
|
|
433
|
+
recommendations.push('Consider rewriting subqueries as JOINs');
|
|
434
|
+
}
|
|
435
|
+
if (performanceWarnings.length > 2) {
|
|
436
|
+
recommendations.push('Query complexity is high - consider breaking into smaller queries');
|
|
437
|
+
}
|
|
438
|
+
return recommendations;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Determine if query execution should be allowed
|
|
442
|
+
*/
|
|
443
|
+
shouldAllowExecution(sql, riskScore, complexityScore, estimatedCost, details) {
|
|
444
|
+
// Check against thresholds
|
|
445
|
+
if (riskScore > this.config.maxRiskScore) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
if (complexityScore > this.config.maxComplexityScore) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
if (estimatedCost > this.config.maxEstimatedCost) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
if (details.estimatedResultSize > this.config.maxResultSetSize) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
// Check for blocking risk factors
|
|
458
|
+
for (const factor of this.riskFactors) {
|
|
459
|
+
if (factor.blocking && factor.pattern.test(sql)) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Update configuration
|
|
467
|
+
*/
|
|
468
|
+
updateConfig(config) {
|
|
469
|
+
this.config = { ...this.config, ...config };
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Add custom risk factor
|
|
473
|
+
*/
|
|
474
|
+
addRiskFactor(factor) {
|
|
475
|
+
this.riskFactors.push(factor);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get current configuration
|
|
479
|
+
*/
|
|
480
|
+
getConfig() {
|
|
481
|
+
return { ...this.config };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
exports.QueryComplexityAnalyzer = QueryComplexityAnalyzer;
|
|
485
|
+
// ==============================================
|
|
486
|
+
// Factory Functions
|
|
487
|
+
// ==============================================
|
|
488
|
+
/**
|
|
489
|
+
* Create a query analyzer with default configuration
|
|
490
|
+
*/
|
|
491
|
+
function createQueryAnalyzer(config) {
|
|
492
|
+
return new QueryComplexityAnalyzer(config);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Create a strict security analyzer
|
|
496
|
+
*/
|
|
497
|
+
function createSecurityAnalyzer() {
|
|
498
|
+
return new QueryComplexityAnalyzer({
|
|
499
|
+
maxRiskScore: 30,
|
|
500
|
+
maxComplexityScore: 50,
|
|
501
|
+
maxEstimatedCost: 100000,
|
|
502
|
+
maxResultSetSize: 1000,
|
|
503
|
+
enableSecurityAnalysis: true,
|
|
504
|
+
enablePerformanceAnalysis: false
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Create a performance-focused analyzer
|
|
509
|
+
*/
|
|
510
|
+
function createPerformanceAnalyzer() {
|
|
511
|
+
return new QueryComplexityAnalyzer({
|
|
512
|
+
maxRiskScore: 100, // Allow all from security perspective
|
|
513
|
+
maxComplexityScore: 60,
|
|
514
|
+
maxEstimatedCost: 500000,
|
|
515
|
+
maxResultSetSize: 5000,
|
|
516
|
+
enableSecurityAnalysis: false,
|
|
517
|
+
enablePerformanceAnalysis: true
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
// ==============================================
|
|
521
|
+
// Default Analyzer Instance
|
|
522
|
+
// ==============================================
|
|
523
|
+
/**
|
|
524
|
+
* Default query analyzer instance
|
|
525
|
+
*/
|
|
526
|
+
exports.defaultQueryAnalyzer = createQueryAnalyzer();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { StructuredLogger } from '../observability/logger.js';
|
|
2
|
+
import type { MetricsCollector } from '../observability/metrics.js';
|
|
3
|
+
export declare enum CircuitBreakerState {
|
|
4
|
+
CLOSED = "CLOSED",
|
|
5
|
+
OPEN = "OPEN",
|
|
6
|
+
HALF_OPEN = "HALF_OPEN"
|
|
7
|
+
}
|
|
8
|
+
export interface CircuitBreakerConfig {
|
|
9
|
+
failureThreshold: number;
|
|
10
|
+
successThreshold: number;
|
|
11
|
+
recoveryTimeout: number;
|
|
12
|
+
windowSize: number;
|
|
13
|
+
errorFilter?: (error: Error) => boolean;
|
|
14
|
+
name?: string;
|
|
15
|
+
logger?: StructuredLogger;
|
|
16
|
+
metrics?: MetricsCollector;
|
|
17
|
+
enableDetailedLogging?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CircuitBreakerStats {
|
|
20
|
+
state: CircuitBreakerState;
|
|
21
|
+
totalCalls: number;
|
|
22
|
+
successfulCalls: number;
|
|
23
|
+
failedCalls: number;
|
|
24
|
+
rejectedCalls: number;
|
|
25
|
+
lastFailureTime: Date | null;
|
|
26
|
+
lastSuccessTime: Date | null;
|
|
27
|
+
nextAttemptTime: Date | null;
|
|
28
|
+
failureRate: number;
|
|
29
|
+
uptime: number;
|
|
30
|
+
}
|
|
31
|
+
export type CircuitBreakerResult<T> = {
|
|
32
|
+
success: true;
|
|
33
|
+
data: T;
|
|
34
|
+
executionTime: number;
|
|
35
|
+
} | {
|
|
36
|
+
success: false;
|
|
37
|
+
error: CircuitBreakerError;
|
|
38
|
+
executionTime: number;
|
|
39
|
+
};
|
|
40
|
+
export declare class CircuitBreakerError extends Error {
|
|
41
|
+
readonly circuitName: string;
|
|
42
|
+
readonly state: CircuitBreakerState;
|
|
43
|
+
readonly timestamp: Date;
|
|
44
|
+
constructor(message: string, circuitName: string, state: CircuitBreakerState);
|
|
45
|
+
}
|
|
46
|
+
export declare class CircuitOpenError extends CircuitBreakerError {
|
|
47
|
+
readonly nextAttemptTime: Date;
|
|
48
|
+
constructor(circuitName: string, nextAttemptTime: Date);
|
|
49
|
+
}
|
|
50
|
+
export declare class CircuitBreaker {
|
|
51
|
+
private state;
|
|
52
|
+
private failureWindow;
|
|
53
|
+
private successCount;
|
|
54
|
+
private totalCalls;
|
|
55
|
+
private successfulCalls;
|
|
56
|
+
private failedCalls;
|
|
57
|
+
private rejectedCalls;
|
|
58
|
+
private lastFailureTime;
|
|
59
|
+
private lastSuccessTime;
|
|
60
|
+
private nextAttemptTime;
|
|
61
|
+
private readonly config;
|
|
62
|
+
private readonly logger;
|
|
63
|
+
private readonly metrics;
|
|
64
|
+
private readonly enableDetailedLogging;
|
|
65
|
+
private lastStateChangeTime;
|
|
66
|
+
constructor(config: CircuitBreakerConfig);
|
|
67
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
68
|
+
executeWithResult<T>(fn: () => Promise<T>): Promise<CircuitBreakerResult<T>>;
|
|
69
|
+
private onSuccess;
|
|
70
|
+
private onFailure;
|
|
71
|
+
private tripCircuit;
|
|
72
|
+
private changeState;
|
|
73
|
+
private recordMetrics;
|
|
74
|
+
private resetFailureCount;
|
|
75
|
+
getStats(): CircuitBreakerStats;
|
|
76
|
+
isCallable(): boolean;
|
|
77
|
+
reset(): void;
|
|
78
|
+
trip(): void;
|
|
79
|
+
getName(): string;
|
|
80
|
+
getState(): CircuitBreakerState;
|
|
81
|
+
}
|
|
82
|
+
export declare function createDatabaseCircuitBreaker(name: string): CircuitBreaker;
|
|
83
|
+
export declare function createApiCircuitBreaker(name: string): CircuitBreaker;
|
|
84
|
+
export declare class CircuitBreakerRegistry {
|
|
85
|
+
private readonly circuits;
|
|
86
|
+
getOrCreate(name: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
87
|
+
getAllCircuits(): Map<string, CircuitBreaker>;
|
|
88
|
+
getAllStats(): Record<string, CircuitBreakerStats>;
|
|
89
|
+
resetAll(): void;
|
|
90
|
+
remove(name: string): boolean;
|
|
91
|
+
clear(): void;
|
|
92
|
+
}
|
|
93
|
+
export declare const defaultCircuitBreakerRegistry: CircuitBreakerRegistry;
|
|
94
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/resilience/circuit-breaker.ts"],"names":[],"mappings":"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"}
|