mastercontroller 1.3.1 → 1.3.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/.claude/settings.local.json +3 -1
- package/MasterAction.js +137 -23
- package/MasterActionFilters.js +197 -92
- package/MasterControl.js +264 -45
- package/MasterHtml.js +226 -143
- package/MasterRequest.js +202 -24
- package/MasterSocket.js +6 -1
- package/MasterTools.js +388 -0
- package/README.md +2288 -369
- package/SECURITY-FIXES-v1.3.2.md +614 -0
- package/docs/SECURITY-AUDIT-ACTION-SYSTEM.md +1374 -0
- package/docs/SECURITY-AUDIT-HTTPS.md +1056 -0
- package/docs/SECURITY-QUICKSTART.md +375 -0
- package/docs/timeout-and-error-handling.md +8 -6
- package/package.json +1 -1
- package/security/SecurityEnforcement.js +241 -0
- package/security/SessionSecurity.js +3 -2
- package/test/security/filters.test.js +276 -0
- package/test/security/https.test.js +214 -0
- package/test/security/path-traversal.test.js +222 -0
- package/test/security/xss.test.js +190 -0
- package/MasterSession.js +0 -208
- package/docs/server-setup-hostname-binding.md +0 -24
- package/docs/server-setup-http.md +0 -32
- package/docs/server-setup-https-credentials.md +0 -32
- package/docs/server-setup-https-env-tls-sni.md +0 -62
- package/docs/server-setup-nginx-reverse-proxy.md +0 -46
package/MasterControl.js
CHANGED
|
@@ -64,6 +64,9 @@ class MasterControl {
|
|
|
64
64
|
_loadedFunc = null
|
|
65
65
|
_tlsOptions = null
|
|
66
66
|
_hstsEnabled = false
|
|
67
|
+
_hstsMaxAge = 31536000 // 1 year default
|
|
68
|
+
_hstsIncludeSubDomains = true
|
|
69
|
+
_hstsPreload = false
|
|
67
70
|
|
|
68
71
|
#loadTransientListClasses(name, params){
|
|
69
72
|
Object.defineProperty(this.requestList, name, {
|
|
@@ -228,6 +231,13 @@ class MasterControl {
|
|
|
228
231
|
|
|
229
232
|
// adds all the server settings needed
|
|
230
233
|
serverSettings(settings){
|
|
234
|
+
// Defensive: Check if server exists (may be called before master.start())
|
|
235
|
+
if (!this.server) {
|
|
236
|
+
console.warn('[MasterControl] serverSettings() called before master.start(server). Settings will be applied when server is set.');
|
|
237
|
+
// Store settings to apply later
|
|
238
|
+
this._pendingServerSettings = settings;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
231
241
|
|
|
232
242
|
if(settings.httpPort || settings.requestTimeout){
|
|
233
243
|
this.server.timeout = settings.requestTimeout;
|
|
@@ -244,6 +254,38 @@ class MasterControl {
|
|
|
244
254
|
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Enable HSTS (HTTP Strict Transport Security) for HTTPS
|
|
259
|
+
* Should only be called for production HTTPS servers
|
|
260
|
+
*
|
|
261
|
+
* @param {Object} options - HSTS configuration options
|
|
262
|
+
* @param {Number} options.maxAge - Max age in seconds (default: 31536000 = 1 year)
|
|
263
|
+
* @param {Boolean} options.includeSubDomains - Include subdomains (default: true)
|
|
264
|
+
* @param {Boolean} options.preload - Enable HSTS preload (default: false)
|
|
265
|
+
* @returns {MasterControl} - Returns this for chaining
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* // Basic usage (1 year, includeSubDomains)
|
|
269
|
+
* master.enableHSTS();
|
|
270
|
+
*
|
|
271
|
+
* // Custom configuration
|
|
272
|
+
* master.enableHSTS({
|
|
273
|
+
* maxAge: 15552000, // 180 days
|
|
274
|
+
* includeSubDomains: true,
|
|
275
|
+
* preload: true // Submit to HSTS preload list
|
|
276
|
+
* });
|
|
277
|
+
*/
|
|
278
|
+
enableHSTS(options = {}) {
|
|
279
|
+
this._hstsEnabled = true;
|
|
280
|
+
this._hstsMaxAge = options.maxAge || 31536000; // 1 year default (matches industry standard)
|
|
281
|
+
this._hstsIncludeSubDomains = options.includeSubDomains !== false; // true by default
|
|
282
|
+
this._hstsPreload = options.preload === true; // false by default
|
|
283
|
+
|
|
284
|
+
console.log(`[MasterControl] HSTS enabled: max-age=${this._hstsMaxAge}${this._hstsIncludeSubDomains ? ', includeSubDomains' : ''}${this._hstsPreload ? ', preload' : ''}`);
|
|
285
|
+
|
|
286
|
+
return this; // Chainable
|
|
287
|
+
}
|
|
288
|
+
|
|
247
289
|
useHTTPServer(port, func){
|
|
248
290
|
if (typeof func === 'function') {
|
|
249
291
|
http.createServer(function (req, res) {
|
|
@@ -256,39 +298,56 @@ class MasterControl {
|
|
|
256
298
|
setupServer(type, credentials ){
|
|
257
299
|
try {
|
|
258
300
|
var $that = this;
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
301
|
+
|
|
302
|
+
// AUTO-LOAD internal framework modules
|
|
303
|
+
// These are required for the framework to function and are loaded transparently
|
|
304
|
+
const internalModules = {
|
|
305
|
+
'MasterPipeline': './MasterPipeline',
|
|
306
|
+
'MasterTimeout': './MasterTimeout',
|
|
307
|
+
'MasterErrorRenderer': './error/MasterErrorRenderer',
|
|
308
|
+
'MasterAction': './MasterAction',
|
|
309
|
+
'MasterActionFilters': './MasterActionFilters',
|
|
310
|
+
'MasterRouter': './MasterRouter',
|
|
311
|
+
'MasterRequest': './MasterRequest',
|
|
312
|
+
'MasterError': './error/MasterError',
|
|
313
|
+
'MasterCors': './MasterCors',
|
|
314
|
+
'SessionSecurity': './security/SessionSecurity',
|
|
315
|
+
'MasterSocket': './MasterSocket',
|
|
316
|
+
'MasterHtml': './MasterHtml',
|
|
317
|
+
'MasterTemplate': './MasterTemplate',
|
|
318
|
+
'MasterTools': './MasterTools',
|
|
319
|
+
'TemplateOverwrite': './TemplateOverwrite'
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
for (const moduleName in internalModules) {
|
|
323
|
+
try {
|
|
324
|
+
const modulePath = internalModules[moduleName];
|
|
325
|
+
const module = require(modulePath);
|
|
326
|
+
|
|
327
|
+
// Special handling for SessionSecurity to avoid circular dependency
|
|
328
|
+
if (moduleName === 'SessionSecurity' && module.MasterSessionSecurity) {
|
|
329
|
+
$that.session = new module.MasterSessionSecurity();
|
|
330
|
+
}
|
|
331
|
+
// Most modules auto-register via master.extend() at module load time
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.error(`[MasterControl] Failed to load ${moduleName}:`, e && e.message);
|
|
334
|
+
}
|
|
282
335
|
}
|
|
283
336
|
|
|
337
|
+
// Initialize global error handlers
|
|
338
|
+
setupGlobalErrorHandlers();
|
|
339
|
+
|
|
284
340
|
// Register core middleware that must run for framework to function
|
|
285
341
|
$that._registerCoreMiddleware();
|
|
286
342
|
|
|
287
343
|
if(type === "http"){
|
|
288
344
|
$that.serverProtocol = "http";
|
|
289
|
-
|
|
345
|
+
const server = http.createServer(async function(req, res) {
|
|
290
346
|
$that.serverRun(req, res);
|
|
291
347
|
});
|
|
348
|
+
// Set server immediately so config can access it
|
|
349
|
+
$that.server = server;
|
|
350
|
+
return server;
|
|
292
351
|
}
|
|
293
352
|
if(type === "https"){
|
|
294
353
|
$that.serverProtocol = "https";
|
|
@@ -297,14 +356,42 @@ class MasterControl {
|
|
|
297
356
|
$that._initializeTlsFromEnv();
|
|
298
357
|
credentials = $that._tlsOptions;
|
|
299
358
|
}
|
|
300
|
-
// Apply secure defaults if missing
|
|
359
|
+
// Apply secure defaults if missing (2026 security standards)
|
|
301
360
|
if(credentials){
|
|
302
|
-
|
|
361
|
+
// Default to TLS 1.3 for security (2026 standard)
|
|
362
|
+
// TLS 1.2 still supported but not default
|
|
363
|
+
if(!credentials.minVersion){
|
|
364
|
+
credentials.minVersion = 'TLSv1.3';
|
|
365
|
+
console.log('[MasterControl] TLS 1.3 enabled by default (recommended for 2026)');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Secure cipher suites (Mozilla Intermediate configuration - 2026)
|
|
369
|
+
// Supports TLS 1.3 and TLS 1.2 for backward compatibility
|
|
370
|
+
if(!credentials.ciphers){
|
|
371
|
+
credentials.ciphers = [
|
|
372
|
+
// TLS 1.3 cipher suites (strongest)
|
|
373
|
+
'TLS_AES_256_GCM_SHA384',
|
|
374
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
375
|
+
'TLS_AES_128_GCM_SHA256',
|
|
376
|
+
// TLS 1.2 cipher suites (backward compatibility)
|
|
377
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
378
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
379
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
380
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
381
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
382
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
383
|
+
].join(':');
|
|
384
|
+
console.log('[MasterControl] Secure cipher suites configured (Mozilla Intermediate)');
|
|
385
|
+
}
|
|
386
|
+
|
|
303
387
|
if(credentials.honorCipherOrder === undefined){ credentials.honorCipherOrder = true; }
|
|
304
388
|
if(!credentials.ALPNProtocols){ credentials.ALPNProtocols = ['h2', 'http/1.1']; }
|
|
305
|
-
|
|
389
|
+
const server = https.createServer(credentials, async function(req, res) {
|
|
306
390
|
$that.serverRun(req, res);
|
|
307
391
|
});
|
|
392
|
+
// Set server immediately so config can access it
|
|
393
|
+
$that.server = server;
|
|
394
|
+
return server;
|
|
308
395
|
}else{
|
|
309
396
|
throw "Credentials needed to setup https"
|
|
310
397
|
}
|
|
@@ -316,18 +403,69 @@ class MasterControl {
|
|
|
316
403
|
}
|
|
317
404
|
}
|
|
318
405
|
|
|
319
|
-
|
|
320
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Creates an HTTP server that 301-redirects to HTTPS counterpart
|
|
408
|
+
* SECURITY: Validates host header to prevent open redirect attacks
|
|
409
|
+
*
|
|
410
|
+
* @param {Number} redirectPort - Port to listen on (usually 80)
|
|
411
|
+
* @param {String} bindHost - Host to bind to (e.g., '0.0.0.0')
|
|
412
|
+
* @param {Array<String>} allowedHosts - Whitelist of allowed hostnames (REQUIRED for security)
|
|
413
|
+
* @returns {http.Server} - HTTP server instance
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* // Production usage (MUST specify allowed hosts)
|
|
417
|
+
* const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
|
|
418
|
+
* 'example.com',
|
|
419
|
+
* 'www.example.com',
|
|
420
|
+
* 'api.example.com'
|
|
421
|
+
* ]);
|
|
422
|
+
*
|
|
423
|
+
* @security CRITICAL: Always provide allowedHosts in production to prevent open redirect attacks
|
|
424
|
+
*/
|
|
425
|
+
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
321
426
|
var $that = this;
|
|
427
|
+
|
|
428
|
+
// Security warning if no hosts specified
|
|
429
|
+
if (allowedHosts.length === 0) {
|
|
430
|
+
console.warn('[MasterControl] ⚠️ SECURITY WARNING: startHttpToHttpsRedirect() called without allowedHosts.');
|
|
431
|
+
console.warn('[MasterControl] This is vulnerable to open redirect attacks. Specify allowed hosts:');
|
|
432
|
+
console.warn('[MasterControl] master.startHttpToHttpsRedirect(80, "0.0.0.0", ["example.com", "www.example.com"])');
|
|
433
|
+
}
|
|
434
|
+
|
|
322
435
|
return http.createServer(function (req, res) {
|
|
323
436
|
try{
|
|
324
437
|
var host = req.headers['host'] || '';
|
|
325
|
-
|
|
438
|
+
var hostname = host.split(':')[0]; // Remove port number
|
|
439
|
+
|
|
440
|
+
// CRITICAL SECURITY: Validate host header to prevent open redirect attacks
|
|
441
|
+
if (allowedHosts.length > 0) {
|
|
442
|
+
if (!allowedHosts.includes(hostname)) {
|
|
443
|
+
logger.warn({
|
|
444
|
+
code: 'MC_SECURITY_INVALID_HOST',
|
|
445
|
+
message: 'HTTP redirect blocked: invalid host header',
|
|
446
|
+
host: hostname,
|
|
447
|
+
ip: req.connection.remoteAddress
|
|
448
|
+
});
|
|
449
|
+
res.statusCode = 400;
|
|
450
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
451
|
+
res.end('Bad Request: Invalid host header');
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Redirect to HTTPS with validated host
|
|
326
457
|
var location = 'https://' + host + req.url;
|
|
327
458
|
res.statusCode = 301;
|
|
328
459
|
res.setHeader('Location', location);
|
|
460
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
329
461
|
res.end();
|
|
330
462
|
}catch(e){
|
|
463
|
+
logger.error({
|
|
464
|
+
code: 'MC_ERR_REDIRECT',
|
|
465
|
+
message: 'HTTP to HTTPS redirect failed',
|
|
466
|
+
error: e.message,
|
|
467
|
+
stack: e.stack
|
|
468
|
+
});
|
|
331
469
|
res.statusCode = 500;
|
|
332
470
|
res.end();
|
|
333
471
|
}
|
|
@@ -447,30 +585,90 @@ class MasterControl {
|
|
|
447
585
|
_registerCoreMiddleware(){
|
|
448
586
|
var $that = this;
|
|
449
587
|
|
|
450
|
-
// 1. Static File Serving
|
|
588
|
+
// 1. Static File Serving (with path traversal protection)
|
|
451
589
|
$that.pipeline.use(async (ctx, next) => {
|
|
452
590
|
if (ctx.isStatic) {
|
|
453
|
-
//
|
|
454
|
-
let
|
|
591
|
+
// SECURITY: Prevent path traversal attacks
|
|
592
|
+
let requestedPath = ctx.request.url;
|
|
593
|
+
|
|
594
|
+
// Normalize the path and resolve it
|
|
595
|
+
const publicRoot = path.resolve($that.root || '.');
|
|
596
|
+
const safePath = path.join(publicRoot, requestedPath);
|
|
597
|
+
const resolvedPath = path.resolve(safePath);
|
|
598
|
+
|
|
599
|
+
// CRITICAL: Ensure resolved path is within public root (prevents ../ attacks)
|
|
600
|
+
if (!resolvedPath.startsWith(publicRoot)) {
|
|
601
|
+
logger.warn({
|
|
602
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
603
|
+
message: 'Path traversal attack blocked',
|
|
604
|
+
requestedPath: requestedPath,
|
|
605
|
+
resolvedPath: resolvedPath,
|
|
606
|
+
ip: ctx.request.connection.remoteAddress
|
|
607
|
+
});
|
|
608
|
+
ctx.response.statusCode = 403;
|
|
609
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
610
|
+
ctx.response.end('Forbidden');
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
455
613
|
|
|
456
|
-
|
|
614
|
+
// SECURITY: Block dotfiles (.env, .git, .htaccess, etc.)
|
|
615
|
+
const filename = path.basename(resolvedPath);
|
|
616
|
+
if (filename.startsWith('.')) {
|
|
617
|
+
logger.warn({
|
|
618
|
+
code: 'MC_SECURITY_DOTFILE_BLOCKED',
|
|
619
|
+
message: 'Dotfile access blocked',
|
|
620
|
+
filename: filename,
|
|
621
|
+
ip: ctx.request.connection.remoteAddress
|
|
622
|
+
});
|
|
623
|
+
ctx.response.statusCode = 403;
|
|
624
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
625
|
+
ctx.response.end('Forbidden');
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Check if file exists
|
|
630
|
+
fs.exists(resolvedPath, function (exist) {
|
|
457
631
|
if (!exist) {
|
|
458
632
|
ctx.response.statusCode = 404;
|
|
459
|
-
ctx.response.
|
|
633
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
634
|
+
ctx.response.end('Not Found');
|
|
460
635
|
return;
|
|
461
636
|
}
|
|
462
637
|
|
|
463
|
-
|
|
464
|
-
|
|
638
|
+
// Get file stats
|
|
639
|
+
let finalPath = resolvedPath;
|
|
640
|
+
const stats = fs.statSync(resolvedPath);
|
|
641
|
+
|
|
642
|
+
// If directory, try to serve index.html
|
|
643
|
+
if (stats.isDirectory()) {
|
|
644
|
+
finalPath = path.join(resolvedPath, 'index.html');
|
|
645
|
+
|
|
646
|
+
// Check if index.html exists
|
|
647
|
+
if (!fs.existsSync(finalPath)) {
|
|
648
|
+
ctx.response.statusCode = 403;
|
|
649
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
650
|
+
ctx.response.end('Forbidden');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
465
653
|
}
|
|
466
654
|
|
|
467
|
-
|
|
655
|
+
// Read and serve the file
|
|
656
|
+
fs.readFile(finalPath, function(err, data) {
|
|
468
657
|
if (err) {
|
|
658
|
+
logger.error({
|
|
659
|
+
code: 'MC_ERR_FILE_READ',
|
|
660
|
+
message: 'Error reading static file',
|
|
661
|
+
path: finalPath,
|
|
662
|
+
error: err.message
|
|
663
|
+
});
|
|
469
664
|
ctx.response.statusCode = 500;
|
|
470
|
-
ctx.response.
|
|
665
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
666
|
+
ctx.response.end('Internal Server Error');
|
|
471
667
|
} else {
|
|
472
|
-
const
|
|
473
|
-
|
|
668
|
+
const ext = path.extname(finalPath);
|
|
669
|
+
const mimeType = $that.router.findMimeType(ext);
|
|
670
|
+
ctx.response.setHeader('Content-Type', mimeType || 'application/octet-stream');
|
|
671
|
+
ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
474
672
|
ctx.response.end(data);
|
|
475
673
|
}
|
|
476
674
|
});
|
|
@@ -489,7 +687,9 @@ class MasterControl {
|
|
|
489
687
|
// 3. Request Body Parsing (always needed)
|
|
490
688
|
$that.pipeline.use(async (ctx, next) => {
|
|
491
689
|
// Parse body using MasterRequest
|
|
492
|
-
|
|
690
|
+
// Pass entire context for backward compatibility (v1.3.x)
|
|
691
|
+
// getRequestParam() will extract request and requrl from context
|
|
692
|
+
const params = await $that.request.getRequestParam(ctx, ctx.response);
|
|
493
693
|
|
|
494
694
|
// Merge parsed params into context
|
|
495
695
|
if (params && params.query) {
|
|
@@ -514,7 +714,15 @@ class MasterControl {
|
|
|
514
714
|
// 4. HSTS Header (if enabled for HTTPS)
|
|
515
715
|
$that.pipeline.use(async (ctx, next) => {
|
|
516
716
|
if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
|
|
517
|
-
|
|
717
|
+
// Use configured HSTS values (not hardcoded)
|
|
718
|
+
let hstsValue = `max-age=${$that._hstsMaxAge}`;
|
|
719
|
+
if ($that._hstsIncludeSubDomains) {
|
|
720
|
+
hstsValue += '; includeSubDomains';
|
|
721
|
+
}
|
|
722
|
+
if ($that._hstsPreload) {
|
|
723
|
+
hstsValue += '; preload';
|
|
724
|
+
}
|
|
725
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
518
726
|
}
|
|
519
727
|
await next();
|
|
520
728
|
});
|
|
@@ -585,6 +793,13 @@ class MasterControl {
|
|
|
585
793
|
|
|
586
794
|
start(server){
|
|
587
795
|
this.server = server;
|
|
796
|
+
|
|
797
|
+
// Apply any pending server settings that were called before start()
|
|
798
|
+
if (this._pendingServerSettings) {
|
|
799
|
+
console.log('[MasterControl] Applying pending server settings');
|
|
800
|
+
this.serverSettings(this._pendingServerSettings);
|
|
801
|
+
this._pendingServerSettings = null;
|
|
802
|
+
}
|
|
588
803
|
}
|
|
589
804
|
|
|
590
805
|
startMVC(foldername){
|
|
@@ -619,7 +834,6 @@ class MasterControl {
|
|
|
619
834
|
'MasterRouter': './MasterRouter',
|
|
620
835
|
'MasterRequest': './MasterRequest',
|
|
621
836
|
'MasterCors': './MasterCors',
|
|
622
|
-
'MasterSession': './MasterSession',
|
|
623
837
|
'SessionSecurity': './security/SessionSecurity',
|
|
624
838
|
'MasterSocket': './MasterSocket',
|
|
625
839
|
'MasterHtml': './MasterHtml',
|
|
@@ -631,7 +845,12 @@ class MasterControl {
|
|
|
631
845
|
for(var i = 0; i < requiredList.length; i++){
|
|
632
846
|
const moduleName = requiredList[i];
|
|
633
847
|
const modulePath = modulePathMap[moduleName] || './' + moduleName;
|
|
634
|
-
require(modulePath);
|
|
848
|
+
const module = require(modulePath);
|
|
849
|
+
|
|
850
|
+
// Special handling for SessionSecurity to avoid circular dependency
|
|
851
|
+
if (moduleName === 'SessionSecurity' && module.MasterSessionSecurity) {
|
|
852
|
+
this.session = new module.MasterSessionSecurity();
|
|
853
|
+
}
|
|
635
854
|
}
|
|
636
855
|
}
|
|
637
856
|
}
|