xypriss 2.3.7 → 3.1.0
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/README.md +21 -19
- package/dist/cjs/mods/security/src/index.js +1 -1
- package/dist/cjs/src/cluster/modules/CrossPlatformMemory.js +2 -2
- package/dist/cjs/src/cluster/modules/CrossPlatformMemory.js.map +1 -1
- package/dist/cjs/src/middleware/built-in/BuiltInMiddleware.js +123 -14
- package/dist/cjs/src/middleware/built-in/BuiltInMiddleware.js.map +1 -1
- package/dist/cjs/src/middleware/built-in/security/BrowserOnlyProtector.js +552 -0
- package/dist/cjs/src/middleware/built-in/security/BrowserOnlyProtector.js.map +1 -0
- package/dist/cjs/src/middleware/built-in/security/RequestSignatureProtector.js +465 -0
- package/dist/cjs/src/middleware/built-in/security/RequestSignatureProtector.js.map +1 -0
- package/dist/cjs/src/middleware/built-in/security/TerminalOnlyProtector.js +477 -0
- package/dist/cjs/src/middleware/built-in/security/TerminalOnlyProtector.js.map +1 -0
- package/dist/cjs/src/middleware/security-middleware.js +257 -91
- package/dist/cjs/src/middleware/security-middleware.js.map +1 -1
- package/dist/cjs/src/server/components/fastapi/templates/redirectTemp.js +1 -1
- package/dist/cjs/src/server/const/default.js +1 -1
- package/dist/cjs/src/server/const/default.js.map +1 -1
- package/dist/esm/mods/security/src/index.js +1 -1
- package/dist/esm/src/cluster/modules/CrossPlatformMemory.js +2 -2
- package/dist/esm/src/cluster/modules/CrossPlatformMemory.js.map +1 -1
- package/dist/esm/src/middleware/built-in/BuiltInMiddleware.js +123 -14
- package/dist/esm/src/middleware/built-in/BuiltInMiddleware.js.map +1 -1
- package/dist/esm/src/middleware/built-in/security/BrowserOnlyProtector.js +550 -0
- package/dist/esm/src/middleware/built-in/security/BrowserOnlyProtector.js.map +1 -0
- package/dist/esm/src/middleware/built-in/security/RequestSignatureProtector.js +444 -0
- package/dist/esm/src/middleware/built-in/security/RequestSignatureProtector.js.map +1 -0
- package/dist/esm/src/middleware/built-in/security/TerminalOnlyProtector.js +475 -0
- package/dist/esm/src/middleware/built-in/security/TerminalOnlyProtector.js.map +1 -0
- package/dist/esm/src/middleware/security-middleware.js +257 -91
- package/dist/esm/src/middleware/security-middleware.js.map +1 -1
- package/dist/esm/src/server/components/fastapi/templates/redirectTemp.js +1 -1
- package/dist/esm/src/server/const/default.js +1 -1
- package/dist/esm/src/server/const/default.js.map +1 -1
- package/dist/index.d.ts +268 -10
- package/package.json +6 -5
- package/scripts/install-memory-cli.js +1 -1
|
@@ -32,19 +32,53 @@ class SecurityMiddleware {
|
|
|
32
32
|
this.csrf = config.csrf !== false ? config.csrf || true : false;
|
|
33
33
|
this.helmet = config.helmet !== false ? config.helmet || true : false;
|
|
34
34
|
this.xss = config.xss !== false ? config.xss || true : false;
|
|
35
|
-
this.sqlInjection =
|
|
36
|
-
|
|
37
|
-
this.
|
|
35
|
+
this.sqlInjection =
|
|
36
|
+
config.sqlInjection !== false ? config.sqlInjection || true : false;
|
|
37
|
+
this.pathTraversal =
|
|
38
|
+
config.pathTraversal !== false
|
|
39
|
+
? config.pathTraversal || false
|
|
40
|
+
: false;
|
|
41
|
+
this.commandInjection =
|
|
42
|
+
config.commandInjection !== false
|
|
43
|
+
? config.commandInjection || false
|
|
44
|
+
: false;
|
|
38
45
|
this.xxe = config.xxe !== false ? config.xxe || false : false;
|
|
39
|
-
this.ldapInjection =
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
this.ldapInjection =
|
|
47
|
+
config.ldapInjection !== false
|
|
48
|
+
? config.ldapInjection || false
|
|
49
|
+
: false;
|
|
50
|
+
this.bruteForce =
|
|
51
|
+
config.bruteForce !== false ? config.bruteForce || true : false;
|
|
52
|
+
this.rateLimit =
|
|
53
|
+
config.rateLimit !== false ? config.rateLimit || true : false;
|
|
42
54
|
this.cors = config.cors !== false ? config.cors || true : false;
|
|
43
|
-
this.compression =
|
|
55
|
+
this.compression =
|
|
56
|
+
config.compression !== false ? config.compression || true : false;
|
|
44
57
|
this.hpp = config.hpp !== false ? config.hpp || true : false;
|
|
45
|
-
this.mongoSanitize =
|
|
58
|
+
this.mongoSanitize =
|
|
59
|
+
config.mongoSanitize !== false
|
|
60
|
+
? config.mongoSanitize || true
|
|
61
|
+
: false;
|
|
46
62
|
this.morgan = config.morgan !== false ? config.morgan || true : false;
|
|
47
|
-
this.slowDown =
|
|
63
|
+
this.slowDown =
|
|
64
|
+
config.slowDown !== false ? config.slowDown || true : false;
|
|
65
|
+
this.browserOnly =
|
|
66
|
+
config.browserOnly !== false ? config.browserOnly || false : false;
|
|
67
|
+
this.terminalOnly =
|
|
68
|
+
config.terminalOnly !== false
|
|
69
|
+
? config.terminalOnly || false
|
|
70
|
+
: false;
|
|
71
|
+
this.requestSignature =
|
|
72
|
+
config.requestSignature !== false
|
|
73
|
+
? config.requestSignature || false
|
|
74
|
+
: false;
|
|
75
|
+
// Validate that both browserOnly and terminalOnly are not enabled simultaneously
|
|
76
|
+
const browserOnlyEnabled = this.isBrowserOnlyEnabled();
|
|
77
|
+
const terminalOnlyEnabled = this.isTerminalOnlyEnabled();
|
|
78
|
+
if (browserOnlyEnabled && terminalOnlyEnabled) {
|
|
79
|
+
throw new Error("Security configuration error: browserOnly and terminalOnly cannot be enabled simultaneously. " +
|
|
80
|
+
"Choose one access control method or disable both.");
|
|
81
|
+
}
|
|
48
82
|
this.encryption = {
|
|
49
83
|
algorithm: "AES-256-GCM",
|
|
50
84
|
keySize: 32,
|
|
@@ -67,7 +101,7 @@ class SecurityMiddleware {
|
|
|
67
101
|
entropy: "high",
|
|
68
102
|
}),
|
|
69
103
|
name: config.authentication?.session?.name ||
|
|
70
|
-
"nehonix.
|
|
104
|
+
"xypriss.nehonix.sid",
|
|
71
105
|
cookie: {
|
|
72
106
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
73
107
|
secure: true,
|
|
@@ -82,45 +116,89 @@ class SecurityMiddleware {
|
|
|
82
116
|
this.routeConfig = config.routeConfig;
|
|
83
117
|
// Initialize security detectors
|
|
84
118
|
this.sqlInjectionDetector = new SQLInjectionDetector({
|
|
85
|
-
strictMode: typeof this.sqlInjection ===
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
strictMode: typeof this.sqlInjection === "object"
|
|
120
|
+
? this.sqlInjection.strictMode
|
|
121
|
+
: false,
|
|
122
|
+
contextualAnalysis: typeof this.sqlInjection === "object"
|
|
123
|
+
? this.sqlInjection.contextualAnalysis
|
|
124
|
+
: true,
|
|
125
|
+
logAttempts: typeof this.sqlInjection === "object"
|
|
126
|
+
? this.sqlInjection.logAttempts
|
|
127
|
+
: true,
|
|
128
|
+
falsePositiveThreshold: typeof this.sqlInjection === "object"
|
|
129
|
+
? this.sqlInjection.falsePositiveThreshold
|
|
130
|
+
: 0.6,
|
|
89
131
|
});
|
|
90
132
|
this.pathTraversalDetector = new PathTraversalDetector({
|
|
91
133
|
enabled: !!this.pathTraversal,
|
|
92
|
-
strictMode: typeof this.pathTraversal ===
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
134
|
+
strictMode: typeof this.pathTraversal === "object"
|
|
135
|
+
? this.pathTraversal.strictMode
|
|
136
|
+
: false,
|
|
137
|
+
logAttempts: typeof this.pathTraversal === "object"
|
|
138
|
+
? this.pathTraversal.logAttempts
|
|
139
|
+
: true,
|
|
140
|
+
blockOnDetection: typeof this.pathTraversal === "object"
|
|
141
|
+
? this.pathTraversal.blockOnDetection
|
|
142
|
+
: true,
|
|
143
|
+
allowedPaths: typeof this.pathTraversal === "object"
|
|
144
|
+
? this.pathTraversal.allowedPaths
|
|
145
|
+
: [],
|
|
146
|
+
allowedExtensions: typeof this.pathTraversal === "object"
|
|
147
|
+
? this.pathTraversal.allowedExtensions
|
|
148
|
+
: [".jpg", ".png", ".pdf", ".txt"],
|
|
149
|
+
maxDepth: typeof this.pathTraversal === "object"
|
|
150
|
+
? this.pathTraversal.maxDepth
|
|
151
|
+
: 3,
|
|
152
|
+
falsePositiveThreshold: typeof this.pathTraversal === "object"
|
|
153
|
+
? this.pathTraversal.falsePositiveThreshold
|
|
154
|
+
: 0.6,
|
|
99
155
|
});
|
|
100
156
|
this.commandInjectionDetector = new CommandInjectionDetector({
|
|
101
157
|
enabled: !!this.commandInjection,
|
|
102
|
-
strictMode: typeof this.commandInjection ===
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
strictMode: typeof this.commandInjection === "object"
|
|
159
|
+
? this.commandInjection.strictMode
|
|
160
|
+
: false,
|
|
161
|
+
logAttempts: typeof this.commandInjection === "object"
|
|
162
|
+
? this.commandInjection.logAttempts
|
|
163
|
+
: true,
|
|
164
|
+
blockOnDetection: typeof this.commandInjection === "object"
|
|
165
|
+
? this.commandInjection.blockOnDetection
|
|
166
|
+
: true,
|
|
167
|
+
contextualAnalysis: typeof this.commandInjection === "object"
|
|
168
|
+
? this.commandInjection.contextualAnalysis
|
|
169
|
+
: true,
|
|
170
|
+
allowedCommands: typeof this.commandInjection === "object"
|
|
171
|
+
? this.commandInjection.allowedCommands
|
|
172
|
+
: [],
|
|
173
|
+
falsePositiveThreshold: typeof this.commandInjection === "object"
|
|
174
|
+
? this.commandInjection.falsePositiveThreshold
|
|
175
|
+
: 0.7,
|
|
108
176
|
});
|
|
109
177
|
this.xxeProtector = new XXEProtector({
|
|
110
178
|
enabled: !!this.xxe,
|
|
111
|
-
strictMode: typeof this.xxe ===
|
|
112
|
-
logAttempts: typeof this.xxe ===
|
|
113
|
-
blockOnDetection: typeof this.xxe ===
|
|
114
|
-
allowDTD: typeof this.xxe ===
|
|
115
|
-
allowExternalEntities: typeof this.xxe ===
|
|
116
|
-
|
|
179
|
+
strictMode: typeof this.xxe === "object" ? this.xxe.strictMode : true,
|
|
180
|
+
logAttempts: typeof this.xxe === "object" ? this.xxe.logAttempts : true,
|
|
181
|
+
blockOnDetection: typeof this.xxe === "object" ? this.xxe.blockOnDetection : true,
|
|
182
|
+
allowDTD: typeof this.xxe === "object" ? this.xxe.allowDTD : false,
|
|
183
|
+
allowExternalEntities: typeof this.xxe === "object"
|
|
184
|
+
? this.xxe.allowExternalEntities
|
|
185
|
+
: false,
|
|
186
|
+
maxEntityExpansions: typeof this.xxe === "object" ? this.xxe.maxEntityExpansions : 0,
|
|
117
187
|
});
|
|
118
188
|
this.ldapInjectionDetector = new LDAPInjectionDetector({
|
|
119
189
|
enabled: !!this.ldapInjection,
|
|
120
|
-
strictMode: typeof this.ldapInjection ===
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
190
|
+
strictMode: typeof this.ldapInjection === "object"
|
|
191
|
+
? this.ldapInjection.strictMode
|
|
192
|
+
: false,
|
|
193
|
+
logAttempts: typeof this.ldapInjection === "object"
|
|
194
|
+
? this.ldapInjection.logAttempts
|
|
195
|
+
: true,
|
|
196
|
+
blockOnDetection: typeof this.ldapInjection === "object"
|
|
197
|
+
? this.ldapInjection.blockOnDetection
|
|
198
|
+
: true,
|
|
199
|
+
falsePositiveThreshold: typeof this.ldapInjection === "object"
|
|
200
|
+
? this.ldapInjection.falsePositiveThreshold
|
|
201
|
+
: 0.6,
|
|
124
202
|
});
|
|
125
203
|
// Initialize all middleware instances
|
|
126
204
|
this.initializeMiddleware();
|
|
@@ -133,20 +211,28 @@ class SecurityMiddleware {
|
|
|
133
211
|
// Helmet for security headers
|
|
134
212
|
if (this.helmet) {
|
|
135
213
|
const helmetConfig = typeof this.helmet === "object" ? this.helmet : {};
|
|
214
|
+
// Prepare CSP configuration with proper merging
|
|
215
|
+
let cspConfig = false;
|
|
216
|
+
if (this.level === "maximum") {
|
|
217
|
+
cspConfig = {
|
|
218
|
+
directives: {
|
|
219
|
+
defaultSrc: ["'self'"],
|
|
220
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
221
|
+
scriptSrc: ["'self'"],
|
|
222
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
else if (helmetConfig.contentSecurityPolicy) {
|
|
227
|
+
// Merge user CSP config with defaults from BuiltInMiddleware
|
|
228
|
+
cspConfig = helmetConfig.contentSecurityPolicy; // BuiltInMiddleware will handle merging
|
|
229
|
+
}
|
|
230
|
+
this.logger.debug("security", "Final cspConfig:", cspConfig);
|
|
136
231
|
this.helmetMiddleware = BuiltInMiddleware.helmet({
|
|
137
|
-
contentSecurityPolicy:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
142
|
-
scriptSrc: ["'self'"],
|
|
143
|
-
imgSrc: ["'self'", "data:", "https:"],
|
|
144
|
-
},
|
|
145
|
-
}
|
|
146
|
-
: helmetConfig.contentSecurityPolicy
|
|
147
|
-
? helmetConfig.contentSecurityPolicy
|
|
148
|
-
: false,
|
|
149
|
-
hsts: this.level !== "basic" || helmetConfig.hsts ? helmetConfig.hsts : undefined,
|
|
232
|
+
contentSecurityPolicy: cspConfig,
|
|
233
|
+
hsts: this.level !== "basic" || helmetConfig.hsts
|
|
234
|
+
? helmetConfig.hsts
|
|
235
|
+
: undefined,
|
|
150
236
|
crossOriginEmbedderPolicy: this.level === "maximum",
|
|
151
237
|
});
|
|
152
238
|
}
|
|
@@ -188,8 +274,10 @@ class SecurityMiddleware {
|
|
|
188
274
|
legacyHeaders: false,
|
|
189
275
|
skip: (req) => {
|
|
190
276
|
// Skip rate limiting for health checks and static assets
|
|
191
|
-
return req.path === "/health" ||
|
|
192
|
-
req.path
|
|
277
|
+
return (req.path === "/health" ||
|
|
278
|
+
req.path === "/ping" ||
|
|
279
|
+
req.path.startsWith("/static/") ||
|
|
280
|
+
req.path.startsWith("/assets/"));
|
|
193
281
|
},
|
|
194
282
|
});
|
|
195
283
|
this.logger.debug("security", `General rate limiting initialized with max: ${maxRequests} requests`);
|
|
@@ -211,6 +299,27 @@ class SecurityMiddleware {
|
|
|
211
299
|
},
|
|
212
300
|
});
|
|
213
301
|
}
|
|
302
|
+
// Browser-only protection
|
|
303
|
+
if (this.isBrowserOnlyEnabled()) {
|
|
304
|
+
const browserOnlyConfig = typeof this.browserOnly === "object" ? this.browserOnly : {};
|
|
305
|
+
this.browserOnlyMiddleware =
|
|
306
|
+
BuiltInMiddleware.browserOnly(browserOnlyConfig);
|
|
307
|
+
}
|
|
308
|
+
// Terminal-only protection
|
|
309
|
+
if (this.isTerminalOnlyEnabled()) {
|
|
310
|
+
const terminalOnlyConfig = typeof this.terminalOnly === "object" ? this.terminalOnly : {};
|
|
311
|
+
this.terminalOnlyMiddleware =
|
|
312
|
+
BuiltInMiddleware.terminalOnly(terminalOnlyConfig);
|
|
313
|
+
}
|
|
314
|
+
// Request signature protection (API authentication)
|
|
315
|
+
if (this.requestSignature) {
|
|
316
|
+
const requestSignatureConfig = typeof this.requestSignature === "object" &&
|
|
317
|
+
this.requestSignature !== null
|
|
318
|
+
? this.requestSignature
|
|
319
|
+
: { secret: "default-secret" }; // This will be overridden by user config
|
|
320
|
+
this.requestSignatureMiddleware =
|
|
321
|
+
BuiltInMiddleware.requestSignature(requestSignatureConfig);
|
|
322
|
+
}
|
|
214
323
|
// Compression middleware
|
|
215
324
|
if (this.compression) {
|
|
216
325
|
const compressionConfig = typeof this.compression === "object" ? this.compression : {};
|
|
@@ -231,19 +340,23 @@ class SecurityMiddleware {
|
|
|
231
340
|
}
|
|
232
341
|
// MongoDB injection protection
|
|
233
342
|
if (this.mongoSanitize) {
|
|
234
|
-
const mongoConfig = typeof this.mongoSanitize === "object"
|
|
343
|
+
const mongoConfig = typeof this.mongoSanitize === "object"
|
|
344
|
+
? this.mongoSanitize
|
|
345
|
+
: {};
|
|
235
346
|
this.mongoSanitizeMiddleware = BuiltInMiddleware.mongoSanitize({
|
|
236
347
|
replaceWith: mongoConfig.replaceWith || "_",
|
|
237
|
-
onSanitize: mongoConfig.onSanitize ||
|
|
238
|
-
|
|
239
|
-
|
|
348
|
+
onSanitize: mongoConfig.onSanitize ||
|
|
349
|
+
(({ req, key }) => {
|
|
350
|
+
this.logger.warn("security", `Sanitized key ${key} in request from ${req.ip}`);
|
|
351
|
+
}),
|
|
240
352
|
});
|
|
241
353
|
}
|
|
242
354
|
// Morgan logging middleware
|
|
243
355
|
if (this.morgan) {
|
|
244
356
|
const morganConfig = typeof this.morgan === "object" ? this.morgan : {};
|
|
245
357
|
this.morganMiddleware = BuiltInMiddleware.morgan({
|
|
246
|
-
skip: morganConfig.skip ||
|
|
358
|
+
skip: morganConfig.skip ||
|
|
359
|
+
((req, res) => res.statusCode < 400),
|
|
247
360
|
stream: morganConfig.stream,
|
|
248
361
|
});
|
|
249
362
|
}
|
|
@@ -253,10 +366,11 @@ class SecurityMiddleware {
|
|
|
253
366
|
this.slowDownMiddleware = BuiltInMiddleware.slowDown({
|
|
254
367
|
windowMs: slowDownConfig.windowMs || 15 * 60 * 1000, // 15 minutes
|
|
255
368
|
delayAfter: slowDownConfig.delayAfter || 100,
|
|
256
|
-
delayMs: slowDownConfig.delayMs ||
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
369
|
+
delayMs: slowDownConfig.delayMs ||
|
|
370
|
+
((used, req) => {
|
|
371
|
+
const delayAfter = req.slowDown?.limit || 100;
|
|
372
|
+
return (used - delayAfter) * 500;
|
|
373
|
+
}),
|
|
260
374
|
});
|
|
261
375
|
}
|
|
262
376
|
}
|
|
@@ -275,57 +389,74 @@ class SecurityMiddleware {
|
|
|
275
389
|
applySecurityStack(req, res, next) {
|
|
276
390
|
this.logger.debug("security", "Starting security middleware stack");
|
|
277
391
|
const middlewareStack = [];
|
|
278
|
-
//
|
|
392
|
+
// 🚨 CRITICAL: Access control middlewares FIRST (before any other processing)
|
|
393
|
+
// These must run before route resolution to block unwanted requests
|
|
394
|
+
// 1. Browser-only protection (blocks cURL and automation tools)
|
|
395
|
+
if (this.isBrowserOnlyEnabled() && this.browserOnlyMiddleware) {
|
|
396
|
+
this.logger.debug("security", "Adding browser-only middleware (FIRST)");
|
|
397
|
+
middlewareStack.push(this.browserOnlyMiddleware);
|
|
398
|
+
}
|
|
399
|
+
// 2. Terminal-only protection (blocks browser requests)
|
|
400
|
+
if (this.isTerminalOnlyEnabled() && this.terminalOnlyMiddleware) {
|
|
401
|
+
this.logger.debug("security", "Adding terminal-only middleware (FIRST)");
|
|
402
|
+
middlewareStack.push(this.terminalOnlyMiddleware);
|
|
403
|
+
}
|
|
404
|
+
// 3. Request signature protection (API authentication)
|
|
405
|
+
if (this.requestSignature && this.requestSignatureMiddleware) {
|
|
406
|
+
this.logger.debug("security", "Adding request signature middleware (FIRST)");
|
|
407
|
+
middlewareStack.push(this.requestSignatureMiddleware);
|
|
408
|
+
}
|
|
409
|
+
// 4. Compression (should be early but after access control)
|
|
279
410
|
if (this.compression && this.compressionMiddleware) {
|
|
280
411
|
this.logger.debug("security", "Adding compression middleware");
|
|
281
412
|
middlewareStack.push(this.compressionMiddleware);
|
|
282
413
|
}
|
|
283
|
-
//
|
|
414
|
+
// 5. Security headers (Helmet)
|
|
284
415
|
if (this.helmet && this.helmetMiddleware) {
|
|
285
416
|
this.logger.debug("security", "Adding helmet middleware");
|
|
286
417
|
middlewareStack.push(this.helmetMiddleware);
|
|
287
418
|
}
|
|
288
|
-
//
|
|
419
|
+
// 6. CORS
|
|
289
420
|
if (this.cors !== false && this.corsMiddleware) {
|
|
290
421
|
this.logger.debug("security", "Adding CORS middleware");
|
|
291
422
|
middlewareStack.push(this.corsMiddleware);
|
|
292
423
|
}
|
|
293
|
-
//
|
|
424
|
+
// 7. Rate limiting (brute force protection - stricter)
|
|
294
425
|
if (this.bruteForce && this.bruteForceMiddleware) {
|
|
295
426
|
this.logger.debug("security", "Adding brute force protection middleware");
|
|
296
427
|
middlewareStack.push(this.bruteForceMiddleware);
|
|
297
428
|
}
|
|
298
|
-
//
|
|
429
|
+
// 8. General rate limiting (less strict)
|
|
299
430
|
if (this.rateLimit && this.rateLimitMiddleware) {
|
|
300
431
|
this.logger.debug("security", "Adding general rate limiting middleware");
|
|
301
432
|
middlewareStack.push(this.rateLimitMiddleware);
|
|
302
433
|
}
|
|
303
|
-
//
|
|
434
|
+
// 9. HTTP Parameter Pollution protection
|
|
304
435
|
if (this.hpp && this.hppMiddleware) {
|
|
305
436
|
this.logger.debug("security", "Adding HPP middleware");
|
|
306
437
|
middlewareStack.push(this.hppMiddleware);
|
|
307
438
|
}
|
|
308
|
-
//
|
|
439
|
+
// 10. MongoDB sanitization
|
|
309
440
|
if (this.mongoSanitize && this.mongoSanitizeMiddleware) {
|
|
310
441
|
this.logger.debug("security", "Adding mongo sanitize middleware");
|
|
311
442
|
middlewareStack.push(this.mongoSanitizeMiddleware);
|
|
312
443
|
}
|
|
313
|
-
//
|
|
444
|
+
// 11. Morgan logging
|
|
314
445
|
if (this.morgan && this.morganMiddleware) {
|
|
315
446
|
this.logger.debug("security", "Adding morgan middleware");
|
|
316
447
|
middlewareStack.push(this.morganMiddleware);
|
|
317
448
|
}
|
|
318
|
-
//
|
|
449
|
+
// 12. Slow down middleware
|
|
319
450
|
if (this.slowDown && this.slowDownMiddleware) {
|
|
320
451
|
this.logger.debug("security", "Adding slow down middleware");
|
|
321
452
|
middlewareStack.push(this.slowDownMiddleware);
|
|
322
453
|
}
|
|
323
|
-
//
|
|
454
|
+
// 13. XSS protection (custom implementation)
|
|
324
455
|
if (this.xss) {
|
|
325
456
|
this.logger.debug("security", "Adding XSS protection middleware");
|
|
326
457
|
middlewareStack.push(this.xssProtection.bind(this));
|
|
327
458
|
}
|
|
328
|
-
//
|
|
459
|
+
// 14. CSRF protection (should be after body parsing)
|
|
329
460
|
if (this.csrf && this.csrfMiddleware) {
|
|
330
461
|
this.logger.debug("security", "Adding CSRF middleware");
|
|
331
462
|
middlewareStack.push(this.csrfMiddleware);
|
|
@@ -505,7 +636,8 @@ class SecurityMiddleware {
|
|
|
505
636
|
detectedPatterns.push("XSS");
|
|
506
637
|
}
|
|
507
638
|
// SQL Injection Detection
|
|
508
|
-
if (this.sqlInjection &&
|
|
639
|
+
if (this.sqlInjection &&
|
|
640
|
+
this.shouldApplySecurityModule(req, this.routeConfig?.sqlInjection)) {
|
|
509
641
|
const sqlResult = this.sqlInjectionDetector.detect(original, currentPath);
|
|
510
642
|
if (sqlResult.isMalicious) {
|
|
511
643
|
threatDetected = true;
|
|
@@ -517,7 +649,8 @@ class SecurityMiddleware {
|
|
|
517
649
|
}
|
|
518
650
|
}
|
|
519
651
|
// Path Traversal Detection
|
|
520
|
-
if (this.pathTraversal &&
|
|
652
|
+
if (this.pathTraversal &&
|
|
653
|
+
this.shouldApplySecurityModule(req, this.routeConfig?.pathTraversal)) {
|
|
521
654
|
const pathResult = this.pathTraversalDetector.detect(original);
|
|
522
655
|
if (pathResult.isMalicious) {
|
|
523
656
|
threatDetected = true;
|
|
@@ -528,7 +661,8 @@ class SecurityMiddleware {
|
|
|
528
661
|
}
|
|
529
662
|
}
|
|
530
663
|
// Command Injection Detection
|
|
531
|
-
if (this.commandInjection &&
|
|
664
|
+
if (this.commandInjection &&
|
|
665
|
+
this.shouldApplySecurityModule(req, this.routeConfig?.commandInjection)) {
|
|
532
666
|
const cmdResult = this.commandInjectionDetector.detect(original);
|
|
533
667
|
if (cmdResult.isMalicious) {
|
|
534
668
|
threatDetected = true;
|
|
@@ -539,7 +673,10 @@ class SecurityMiddleware {
|
|
|
539
673
|
}
|
|
540
674
|
}
|
|
541
675
|
// XXE Detection (for XML content)
|
|
542
|
-
if (this.xxe &&
|
|
676
|
+
if (this.xxe &&
|
|
677
|
+
this.shouldApplySecurityModule(req, this.routeConfig?.xxe) &&
|
|
678
|
+
(original.includes("<?xml") ||
|
|
679
|
+
original.includes("<!DOCTYPE"))) {
|
|
543
680
|
const xxeResult = this.xxeProtector.detect(original);
|
|
544
681
|
if (xxeResult.isMalicious) {
|
|
545
682
|
threatDetected = true;
|
|
@@ -550,7 +687,8 @@ class SecurityMiddleware {
|
|
|
550
687
|
}
|
|
551
688
|
}
|
|
552
689
|
// LDAP Injection Detection
|
|
553
|
-
if (this.ldapInjection &&
|
|
690
|
+
if (this.ldapInjection &&
|
|
691
|
+
this.shouldApplySecurityModule(req, this.routeConfig?.ldapInjection)) {
|
|
554
692
|
const ldapResult = this.ldapInjectionDetector.detect(original);
|
|
555
693
|
if (ldapResult.isMalicious) {
|
|
556
694
|
threatDetected = true;
|
|
@@ -615,6 +753,29 @@ class SecurityMiddleware {
|
|
|
615
753
|
}
|
|
616
754
|
return null;
|
|
617
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Check if browser-only protection is enabled
|
|
758
|
+
*/
|
|
759
|
+
isBrowserOnlyEnabled() {
|
|
760
|
+
if (this.browserOnly === true)
|
|
761
|
+
return true;
|
|
762
|
+
if (typeof this.browserOnly === "object" && this.browserOnly !== null) {
|
|
763
|
+
return this.browserOnly.enable !== false; // Default to true when config provided
|
|
764
|
+
}
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Check if terminal-only protection is enabled
|
|
769
|
+
*/
|
|
770
|
+
isTerminalOnlyEnabled() {
|
|
771
|
+
if (this.terminalOnly === true)
|
|
772
|
+
return true;
|
|
773
|
+
if (typeof this.terminalOnly === "object" &&
|
|
774
|
+
this.terminalOnly !== null) {
|
|
775
|
+
return this.terminalOnly.enable !== false; // Default to true when config provided
|
|
776
|
+
}
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
618
779
|
/**
|
|
619
780
|
* Get security configuration
|
|
620
781
|
*/
|
|
@@ -623,6 +784,9 @@ class SecurityMiddleware {
|
|
|
623
784
|
level: this.level,
|
|
624
785
|
csrf: this.csrf,
|
|
625
786
|
helmet: this.helmet,
|
|
787
|
+
browserOnly: this.browserOnly,
|
|
788
|
+
terminalOnly: this.terminalOnly,
|
|
789
|
+
requestSignature: this.requestSignature,
|
|
626
790
|
xss: this.xss,
|
|
627
791
|
sqlInjection: this.sqlInjection,
|
|
628
792
|
pathTraversal: this.pathTraversal,
|
|
@@ -647,7 +811,7 @@ class SecurityMiddleware {
|
|
|
647
811
|
*/
|
|
648
812
|
matchesRoute(requestPath, requestMethod, pattern) {
|
|
649
813
|
// Handle RoutePattern object
|
|
650
|
-
if (typeof pattern ===
|
|
814
|
+
if (typeof pattern === "object" && "path" in pattern) {
|
|
651
815
|
const routePattern = pattern;
|
|
652
816
|
// Check method if specified
|
|
653
817
|
if (routePattern.methods && routePattern.methods.length > 0) {
|
|
@@ -664,25 +828,25 @@ class SecurityMiddleware {
|
|
|
664
828
|
// Handle string patterns with wildcards
|
|
665
829
|
const patternStr = pattern;
|
|
666
830
|
// Normalize paths by removing trailing slashes for comparison
|
|
667
|
-
const normalizedRequestPath = requestPath.replace(/\/$/,
|
|
668
|
-
const normalizedPattern = patternStr.replace(/\/$/,
|
|
831
|
+
const normalizedRequestPath = requestPath.replace(/\/$/, "");
|
|
832
|
+
const normalizedPattern = patternStr.replace(/\/$/, "");
|
|
669
833
|
// Exact match (after normalization)
|
|
670
834
|
if (normalizedPattern === normalizedRequestPath) {
|
|
671
835
|
return true;
|
|
672
836
|
}
|
|
673
837
|
// Wildcard matching (e.g., /api/* matches /api/anything)
|
|
674
|
-
if (patternStr.includes(
|
|
838
|
+
if (patternStr.includes("*")) {
|
|
675
839
|
// Handle trailing /* specially to match with or without trailing slash
|
|
676
|
-
if (patternStr.endsWith(
|
|
840
|
+
if (patternStr.endsWith("/*")) {
|
|
677
841
|
const prefix = patternStr.slice(0, -2); // Remove /*
|
|
678
842
|
// Match if requestPath starts with prefix, optionally followed by /
|
|
679
|
-
const regex = new RegExp(`^${prefix.replace(/[.+?^${}()|[\]\\]/g,
|
|
843
|
+
const regex = new RegExp(`^${prefix.replace(/[.+?^${}()|[\]\\]/g, "\\$&")}(?:/.*)?$`);
|
|
680
844
|
return regex.test(requestPath);
|
|
681
845
|
}
|
|
682
846
|
else {
|
|
683
847
|
const regexPattern = patternStr
|
|
684
|
-
.replace(/[.+?^${}()|[\]\\]/g,
|
|
685
|
-
.replace(/\*/g,
|
|
848
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&") // Escape special regex chars except *
|
|
849
|
+
.replace(/\*/g, ".*"); // Convert * to .*
|
|
686
850
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
687
851
|
return regex.test(requestPath);
|
|
688
852
|
}
|
|
@@ -700,17 +864,19 @@ class SecurityMiddleware {
|
|
|
700
864
|
if (!moduleConfig) {
|
|
701
865
|
return true; // Apply by default if no route config
|
|
702
866
|
}
|
|
703
|
-
const requestPath = req.path || req.url ||
|
|
704
|
-
const requestMethod = req.method ||
|
|
867
|
+
const requestPath = req.path || req.url || "";
|
|
868
|
+
const requestMethod = req.method || "GET";
|
|
705
869
|
// Check includeRoutes first (whitelist approach)
|
|
706
|
-
if (moduleConfig.includeRoutes &&
|
|
870
|
+
if (moduleConfig.includeRoutes &&
|
|
871
|
+
moduleConfig.includeRoutes.length > 0) {
|
|
707
872
|
// Only apply if route is in the include list
|
|
708
|
-
return moduleConfig.includeRoutes.some(pattern => this.matchesRoute(requestPath, requestMethod, pattern));
|
|
873
|
+
return moduleConfig.includeRoutes.some((pattern) => this.matchesRoute(requestPath, requestMethod, pattern));
|
|
709
874
|
}
|
|
710
875
|
// Check excludeRoutes (blacklist approach)
|
|
711
|
-
if (moduleConfig.excludeRoutes &&
|
|
876
|
+
if (moduleConfig.excludeRoutes &&
|
|
877
|
+
moduleConfig.excludeRoutes.length > 0) {
|
|
712
878
|
// Don't apply if route is in the exclude list
|
|
713
|
-
const isExcluded = moduleConfig.excludeRoutes.some(pattern => this.matchesRoute(requestPath, requestMethod, pattern));
|
|
879
|
+
const isExcluded = moduleConfig.excludeRoutes.some((pattern) => this.matchesRoute(requestPath, requestMethod, pattern));
|
|
714
880
|
return !isExcluded;
|
|
715
881
|
}
|
|
716
882
|
return true; // Apply by default
|