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