mastercontroller 1.2.12 → 1.2.14
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/.claude/settings.local.json +12 -0
- package/MasterAction.js +297 -73
- package/MasterControl.js +112 -19
- package/MasterHtml.js +101 -14
- package/MasterRouter.js +281 -66
- package/MasterTemplate.js +96 -3
- package/README.md +0 -44
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/{MasterError.js → error/MasterError.js} +2 -2
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/SSRErrorHandler.js +273 -0
- package/monitoring/MasterCache.js +400 -0
- package/monitoring/MasterMemoryMonitor.js +188 -0
- package/monitoring/MasterProfiler.js +409 -0
- package/monitoring/PerformanceMonitor.js +233 -0
- package/package.json +3 -3
- package/security/CSPConfig.js +319 -0
- package/security/EventHandlerValidator.js +464 -0
- package/security/MasterSanitizer.js +429 -0
- package/security/MasterValidator.js +546 -0
- package/security/SecurityMiddleware.js +486 -0
- package/security/SessionSecurity.js +416 -0
- package/ssr/hydration-client.js +93 -0
- package/ssr/runtime-ssr.cjs +553 -0
- package/ssr/ssr-shims.js +73 -0
- package/examples/FileServingExample.js +0 -88
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
// version 1.0.1
|
|
2
|
+
// MasterController Input Validator - SQL Injection, Path Traversal, Command Injection Protection
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Comprehensive input validation to prevent:
|
|
6
|
+
* - SQL Injection
|
|
7
|
+
* - NoSQL Injection
|
|
8
|
+
* - Path Traversal
|
|
9
|
+
* - Command Injection
|
|
10
|
+
* - LDAP Injection
|
|
11
|
+
* - XML Injection
|
|
12
|
+
* - Header Injection
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
16
|
+
const { escapeHTML } = require('./MasterSanitizer');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// SQL injection patterns
|
|
20
|
+
const SQL_INJECTION_PATTERNS = [
|
|
21
|
+
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|EXECUTE|UNION|DECLARE)\b)/gi,
|
|
22
|
+
/(--|;|\/\*|\*\/|xp_|sp_)/gi,
|
|
23
|
+
/('|(\\'))|("|(\\"))(\s|$)/gi,
|
|
24
|
+
/(\bOR\b|\bAND\b).*?=.*?/gi,
|
|
25
|
+
/(0x[0-9a-f]+)/gi
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// NoSQL injection patterns (MongoDB, etc.)
|
|
29
|
+
const NOSQL_INJECTION_PATTERNS = [
|
|
30
|
+
/\$where/gi,
|
|
31
|
+
/\$ne/gi,
|
|
32
|
+
/\$gt/gi,
|
|
33
|
+
/\$lt/gi,
|
|
34
|
+
/\$regex/gi,
|
|
35
|
+
/\$nin/gi,
|
|
36
|
+
/\$in/gi
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Command injection patterns
|
|
40
|
+
const COMMAND_INJECTION_PATTERNS = [
|
|
41
|
+
/[;&|`$()]/g,
|
|
42
|
+
/\n/g,
|
|
43
|
+
/\r/g
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Path traversal patterns
|
|
47
|
+
const PATH_TRAVERSAL_PATTERNS = [
|
|
48
|
+
/\.\./g,
|
|
49
|
+
/\.\/\./g,
|
|
50
|
+
/%2e%2e/gi,
|
|
51
|
+
/%252e/gi,
|
|
52
|
+
/\.\.%2f/gi,
|
|
53
|
+
/\.\.%5c/gi
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// LDAP injection patterns
|
|
57
|
+
const LDAP_INJECTION_PATTERNS = [
|
|
58
|
+
/[*()\\]/g,
|
|
59
|
+
/\x00/g
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Email validation regex
|
|
63
|
+
const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
64
|
+
|
|
65
|
+
// URL validation regex
|
|
66
|
+
const URL_REGEX = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
|
|
67
|
+
|
|
68
|
+
// UUID validation regex
|
|
69
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
70
|
+
|
|
71
|
+
class MasterValidator {
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.throwOnError = options.throwOnError || false;
|
|
74
|
+
this.logViolations = options.logViolations !== false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate and sanitize string input
|
|
79
|
+
*/
|
|
80
|
+
validateString(input, options = {}) {
|
|
81
|
+
const {
|
|
82
|
+
minLength = 0,
|
|
83
|
+
maxLength = 10000,
|
|
84
|
+
allowEmpty = true,
|
|
85
|
+
trim = true,
|
|
86
|
+
pattern = null,
|
|
87
|
+
name = 'input'
|
|
88
|
+
} = options;
|
|
89
|
+
|
|
90
|
+
// Type check
|
|
91
|
+
if (typeof input !== 'string') {
|
|
92
|
+
return this._handleError('INVALID_TYPE', `${name} must be a string`, { input });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let sanitized = trim ? input.trim() : input;
|
|
96
|
+
|
|
97
|
+
// Empty check
|
|
98
|
+
if (!allowEmpty && sanitized.length === 0) {
|
|
99
|
+
return this._handleError('EMPTY_STRING', `${name} cannot be empty`, { input });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Length check
|
|
103
|
+
if (sanitized.length < minLength) {
|
|
104
|
+
return this._handleError('TOO_SHORT', `${name} must be at least ${minLength} characters`, { input });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (sanitized.length > maxLength) {
|
|
108
|
+
sanitized = sanitized.substring(0, maxLength);
|
|
109
|
+
this._logWarning('STRING_TRUNCATED', `${name} was truncated to ${maxLength} characters`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Pattern check
|
|
113
|
+
if (pattern && !pattern.test(sanitized)) {
|
|
114
|
+
return this._handleError('PATTERN_MISMATCH', `${name} does not match required pattern`, { input });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { valid: true, value: sanitized };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate and sanitize integer
|
|
122
|
+
*/
|
|
123
|
+
validateInteger(input, options = {}) {
|
|
124
|
+
const {
|
|
125
|
+
min = Number.MIN_SAFE_INTEGER,
|
|
126
|
+
max = Number.MAX_SAFE_INTEGER,
|
|
127
|
+
name = 'input'
|
|
128
|
+
} = options;
|
|
129
|
+
|
|
130
|
+
const parsed = parseInt(input, 10);
|
|
131
|
+
|
|
132
|
+
if (isNaN(parsed)) {
|
|
133
|
+
return this._handleError('INVALID_INTEGER', `${name} must be a valid integer`, { input });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (parsed < min) {
|
|
137
|
+
return this._handleError('TOO_SMALL', `${name} must be at least ${min}`, { input });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (parsed > max) {
|
|
141
|
+
return this._handleError('TOO_LARGE', `${name} must be at most ${max}`, { input });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { valid: true, value: parsed };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validate email address
|
|
149
|
+
*/
|
|
150
|
+
validateEmail(input, options = {}) {
|
|
151
|
+
const { name = 'email' } = options;
|
|
152
|
+
|
|
153
|
+
if (typeof input !== 'string') {
|
|
154
|
+
return this._handleError('INVALID_TYPE', `${name} must be a string`, { input });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const trimmed = input.trim().toLowerCase();
|
|
158
|
+
|
|
159
|
+
if (!EMAIL_REGEX.test(trimmed)) {
|
|
160
|
+
return this._handleError('INVALID_EMAIL', `${name} is not a valid email address`, { input });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { valid: true, value: trimmed };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validate URL
|
|
168
|
+
*/
|
|
169
|
+
validateURL(input, options = {}) {
|
|
170
|
+
const { name = 'url', allowedProtocols = ['http:', 'https:'] } = options;
|
|
171
|
+
|
|
172
|
+
if (typeof input !== 'string') {
|
|
173
|
+
return this._handleError('INVALID_TYPE', `${name} must be a string`, { input });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const trimmed = input.trim();
|
|
177
|
+
|
|
178
|
+
if (!URL_REGEX.test(trimmed)) {
|
|
179
|
+
return this._handleError('INVALID_URL', `${name} is not a valid URL`, { input });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check protocol
|
|
183
|
+
try {
|
|
184
|
+
const url = new URL(trimmed);
|
|
185
|
+
if (!allowedProtocols.includes(url.protocol)) {
|
|
186
|
+
return this._handleError('INVALID_PROTOCOL', `${name} protocol must be ${allowedProtocols.join(' or ')}`, { input });
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
return this._handleError('INVALID_URL', `${name} is not a valid URL`, { input });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { valid: true, value: trimmed };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate UUID
|
|
197
|
+
*/
|
|
198
|
+
validateUUID(input, options = {}) {
|
|
199
|
+
const { name = 'uuid' } = options;
|
|
200
|
+
|
|
201
|
+
if (typeof input !== 'string') {
|
|
202
|
+
return this._handleError('INVALID_TYPE', `${name} must be a string`, { input });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!UUID_REGEX.test(input.trim())) {
|
|
206
|
+
return this._handleError('INVALID_UUID', `${name} is not a valid UUID`, { input });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { valid: true, value: input.trim() };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check for SQL injection attempts
|
|
214
|
+
*/
|
|
215
|
+
detectSQLInjection(input, options = {}) {
|
|
216
|
+
if (typeof input !== 'string') {
|
|
217
|
+
return { safe: true, value: input };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const pattern of SQL_INJECTION_PATTERNS) {
|
|
221
|
+
if (pattern.test(input)) {
|
|
222
|
+
this._logViolation('SQL_INJECTION_ATTEMPT', input, pattern);
|
|
223
|
+
return { safe: false, threat: 'SQL_INJECTION', pattern: pattern.toString() };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { safe: true, value: input };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check for NoSQL injection attempts
|
|
232
|
+
*/
|
|
233
|
+
detectNoSQLInjection(input) {
|
|
234
|
+
if (typeof input === 'object' && input !== null) {
|
|
235
|
+
const json = JSON.stringify(input);
|
|
236
|
+
for (const pattern of NOSQL_INJECTION_PATTERNS) {
|
|
237
|
+
if (pattern.test(json)) {
|
|
238
|
+
this._logViolation('NOSQL_INJECTION_ATTEMPT', json, pattern);
|
|
239
|
+
return { safe: false, threat: 'NOSQL_INJECTION', pattern: pattern.toString() };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { safe: true, value: input };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check for command injection attempts
|
|
249
|
+
*/
|
|
250
|
+
detectCommandInjection(input, options = {}) {
|
|
251
|
+
if (typeof input !== 'string') {
|
|
252
|
+
return { safe: true, value: input };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const pattern of COMMAND_INJECTION_PATTERNS) {
|
|
256
|
+
if (pattern.test(input)) {
|
|
257
|
+
this._logViolation('COMMAND_INJECTION_ATTEMPT', input, pattern);
|
|
258
|
+
return { safe: false, threat: 'COMMAND_INJECTION', pattern: pattern.toString() };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { safe: true, value: input };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check for path traversal attempts
|
|
267
|
+
*/
|
|
268
|
+
detectPathTraversal(input, options = {}) {
|
|
269
|
+
if (typeof input !== 'string') {
|
|
270
|
+
return { safe: true, value: input };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const pattern of PATH_TRAVERSAL_PATTERNS) {
|
|
274
|
+
if (pattern.test(input)) {
|
|
275
|
+
this._logViolation('PATH_TRAVERSAL_ATTEMPT', input, pattern);
|
|
276
|
+
return { safe: false, threat: 'PATH_TRAVERSAL', pattern: pattern.toString() };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { safe: true, value: input };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Sanitize file path to prevent path traversal
|
|
285
|
+
*/
|
|
286
|
+
sanitizeFilePath(input, options = {}) {
|
|
287
|
+
const { basePath = null, name = 'path' } = options;
|
|
288
|
+
|
|
289
|
+
if (typeof input !== 'string') {
|
|
290
|
+
return this._handleError('INVALID_TYPE', `${name} must be a string`, { input });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for path traversal
|
|
294
|
+
const traversalCheck = this.detectPathTraversal(input);
|
|
295
|
+
if (!traversalCheck.safe) {
|
|
296
|
+
return this._handleError('PATH_TRAVERSAL', `${name} contains path traversal attempt`, { input });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Normalize path
|
|
300
|
+
const normalized = path.normalize(input);
|
|
301
|
+
|
|
302
|
+
// If basePath provided, ensure path is within it
|
|
303
|
+
if (basePath) {
|
|
304
|
+
const resolved = path.resolve(basePath, normalized);
|
|
305
|
+
const base = path.resolve(basePath);
|
|
306
|
+
|
|
307
|
+
if (!resolved.startsWith(base)) {
|
|
308
|
+
return this._handleError('PATH_OUTSIDE_BASE', `${name} is outside allowed directory`, { input, basePath });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { valid: true, value: resolved };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { valid: true, value: normalized };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Sanitize SQL input (use parameterized queries instead when possible)
|
|
319
|
+
*/
|
|
320
|
+
sanitizeSQL(input) {
|
|
321
|
+
if (typeof input !== 'string') {
|
|
322
|
+
return input;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Escape single quotes
|
|
326
|
+
let sanitized = input.replace(/'/g, "''");
|
|
327
|
+
|
|
328
|
+
// Remove SQL comments
|
|
329
|
+
sanitized = sanitized.replace(/--.*$/gm, '');
|
|
330
|
+
sanitized = sanitized.replace(/\/\*.*?\*\//gs, '');
|
|
331
|
+
|
|
332
|
+
// Check for injection after sanitization
|
|
333
|
+
const check = this.detectSQLInjection(sanitized);
|
|
334
|
+
if (!check.safe) {
|
|
335
|
+
this._logWarning('SQL_INJECTION_AFTER_SANITIZATION', 'Input still contains SQL patterns after sanitization');
|
|
336
|
+
return ''; // Return empty string if still dangerous
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return sanitized;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Validate route parameters
|
|
344
|
+
*/
|
|
345
|
+
validateRouteParams(params, schema = {}) {
|
|
346
|
+
const sanitized = {};
|
|
347
|
+
const errors = [];
|
|
348
|
+
|
|
349
|
+
for (const [key, value] of Object.entries(params)) {
|
|
350
|
+
const rules = schema[key];
|
|
351
|
+
|
|
352
|
+
if (!rules) {
|
|
353
|
+
// No validation rules, sanitize as string
|
|
354
|
+
sanitized[key] = escapeHTML(String(value));
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let result;
|
|
359
|
+
|
|
360
|
+
switch (rules.type) {
|
|
361
|
+
case 'string':
|
|
362
|
+
result = this.validateString(value, rules);
|
|
363
|
+
break;
|
|
364
|
+
case 'integer':
|
|
365
|
+
result = this.validateInteger(value, rules);
|
|
366
|
+
break;
|
|
367
|
+
case 'email':
|
|
368
|
+
result = this.validateEmail(value, rules);
|
|
369
|
+
break;
|
|
370
|
+
case 'url':
|
|
371
|
+
result = this.validateURL(value, rules);
|
|
372
|
+
break;
|
|
373
|
+
case 'uuid':
|
|
374
|
+
result = this.validateUUID(value, rules);
|
|
375
|
+
break;
|
|
376
|
+
default:
|
|
377
|
+
result = { valid: true, value: escapeHTML(String(value)) };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (result.valid) {
|
|
381
|
+
sanitized[key] = result.value;
|
|
382
|
+
} else {
|
|
383
|
+
errors.push({ param: key, error: result.error });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
valid: errors.length === 0,
|
|
389
|
+
params: sanitized,
|
|
390
|
+
errors
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Validate request body
|
|
396
|
+
*/
|
|
397
|
+
validateRequestBody(body, schema = {}) {
|
|
398
|
+
return this.validateRouteParams(body, schema);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Sanitize object recursively
|
|
403
|
+
*/
|
|
404
|
+
sanitizeObject(obj) {
|
|
405
|
+
if (obj === null || obj === undefined) {
|
|
406
|
+
return obj;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (Array.isArray(obj)) {
|
|
410
|
+
return obj.map(item => this.sanitizeObject(item));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (typeof obj === 'object') {
|
|
414
|
+
const sanitized = {};
|
|
415
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
416
|
+
// Skip internal properties
|
|
417
|
+
if (key.startsWith('_') || key.startsWith('__')) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
422
|
+
}
|
|
423
|
+
return sanitized;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (typeof obj === 'string') {
|
|
427
|
+
return escapeHTML(obj);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return obj;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Handle validation error
|
|
435
|
+
*/
|
|
436
|
+
_handleError(code, message, context = {}) {
|
|
437
|
+
const error = {
|
|
438
|
+
valid: false,
|
|
439
|
+
error: { code, message, context }
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
if (this.logViolations) {
|
|
443
|
+
logger.warn({
|
|
444
|
+
code: `MC_VALIDATION_${code}`,
|
|
445
|
+
message: message,
|
|
446
|
+
...context
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (this.throwOnError) {
|
|
451
|
+
throw new Error(message);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return error;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Log validation warning
|
|
459
|
+
*/
|
|
460
|
+
_logWarning(code, message, context = {}) {
|
|
461
|
+
if (this.logViolations) {
|
|
462
|
+
logger.warn({
|
|
463
|
+
code: `MC_VALIDATION_${code}`,
|
|
464
|
+
message: message,
|
|
465
|
+
...context
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Log security violation
|
|
472
|
+
*/
|
|
473
|
+
_logViolation(type, input, pattern) {
|
|
474
|
+
if (this.logViolations) {
|
|
475
|
+
logger.error({
|
|
476
|
+
code: `MC_SECURITY_${type}`,
|
|
477
|
+
message: `Security violation detected: ${type}`,
|
|
478
|
+
input: input.substring(0, 100), // Log first 100 chars only
|
|
479
|
+
pattern: pattern.toString(),
|
|
480
|
+
timestamp: new Date().toISOString()
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Create singleton instance
|
|
487
|
+
const validator = new MasterValidator();
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Quick validation functions
|
|
491
|
+
*/
|
|
492
|
+
|
|
493
|
+
function validateString(input, options) {
|
|
494
|
+
return validator.validateString(input, options);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function validateInteger(input, options) {
|
|
498
|
+
return validator.validateInteger(input, options);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function validateEmail(input, options) {
|
|
502
|
+
return validator.validateEmail(input, options);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function validateURL(input, options) {
|
|
506
|
+
return validator.validateURL(input, options);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function sanitizeSQL(input) {
|
|
510
|
+
return validator.sanitizeSQL(input);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function sanitizeFilePath(input, options) {
|
|
514
|
+
return validator.sanitizeFilePath(input, options);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function validateRouteParams(params, schema) {
|
|
518
|
+
return validator.validateRouteParams(params, schema);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function detectSQLInjection(input) {
|
|
522
|
+
return validator.detectSQLInjection(input);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function detectPathTraversal(input) {
|
|
526
|
+
return validator.detectPathTraversal(input);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function detectCommandInjection(input) {
|
|
530
|
+
return validator.detectCommandInjection(input);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
module.exports = {
|
|
534
|
+
MasterValidator,
|
|
535
|
+
validator,
|
|
536
|
+
validateString,
|
|
537
|
+
validateInteger,
|
|
538
|
+
validateEmail,
|
|
539
|
+
validateURL,
|
|
540
|
+
sanitizeSQL,
|
|
541
|
+
sanitizeFilePath,
|
|
542
|
+
validateRouteParams,
|
|
543
|
+
detectSQLInjection,
|
|
544
|
+
detectPathTraversal,
|
|
545
|
+
detectCommandInjection
|
|
546
|
+
};
|