mastercontroller 1.3.2 → 1.3.3
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 +4 -1
- package/MasterAction.js +21 -4
- package/MasterActionFilters.js +17 -2
- package/MasterControl.js +35 -8
- package/MasterCors.js +1 -2
- package/MasterHtml.js +17 -2
- package/MasterPipeline.js +2 -3
- package/MasterRequest.js +1 -2
- package/MasterRouter.js +1 -2
- package/MasterSocket.js +1 -2
- package/MasterTemp.js +1 -2
- package/MasterTimeout.js +2 -4
- package/TemplateOverwrite.js +1 -1
- package/error/MasterError.js +1 -2
- package/error/MasterErrorRenderer.js +1 -2
- package/package.json +1 -1
- package/security/SessionSecurity.js +3 -3
- package/SECURITY-FIXES-v1.3.2.md +0 -614
package/MasterAction.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
// version 0.0.23
|
|
3
3
|
|
|
4
|
-
var master = require('./MasterControl');
|
|
5
4
|
var fileserver = require('fs');
|
|
6
5
|
var toolClass = require('./MasterTools');
|
|
7
6
|
var tempClass = require('./MasterTemplate');
|
|
@@ -26,9 +25,18 @@ const { validator, validateRequestBody, sanitizeObject } = require('./security/M
|
|
|
26
25
|
const { sanitizeUserHTML, escapeHTML } = require('./security/MasterSanitizer');
|
|
27
26
|
|
|
28
27
|
class MasterAction{
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
// Lazy-load master to avoid circular dependency
|
|
30
|
+
// Static getter ensures single instance (Singleton pattern - Google style)
|
|
31
|
+
static get _master() {
|
|
32
|
+
if (!MasterAction.__masterCache) {
|
|
33
|
+
MasterAction.__masterCache = require('./MasterControl');
|
|
34
|
+
}
|
|
35
|
+
return MasterAction.__masterCache;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
getView(location, data){
|
|
31
|
-
var actionUrl =
|
|
39
|
+
var actionUrl = MasterAction._master.root + location;
|
|
32
40
|
const fileResult = safeReadFile(fileserver, actionUrl);
|
|
33
41
|
|
|
34
42
|
if (!fileResult.success) {
|
|
@@ -598,5 +606,14 @@ class MasterAction{
|
|
|
598
606
|
|
|
599
607
|
}
|
|
600
608
|
|
|
609
|
+
// Export for MasterControl and register after event loop (prevents circular dependency)
|
|
610
|
+
// This is the Lazy Registration pattern used by Spring Framework, Angular, Google Guice
|
|
611
|
+
module.exports = MasterAction;
|
|
601
612
|
|
|
602
|
-
master
|
|
613
|
+
// Use setImmediate to register after master is fully loaded
|
|
614
|
+
setImmediate(() => {
|
|
615
|
+
const master = require('./MasterControl');
|
|
616
|
+
if (master && master.extendController) {
|
|
617
|
+
master.extendController(MasterAction);
|
|
618
|
+
}
|
|
619
|
+
});
|
package/MasterActionFilters.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// version 2.0 - FIXED: Instance-level filters, async support, multiple filters
|
|
2
|
-
var master = require('./MasterControl');
|
|
3
2
|
const { logger } = require('./error/MasterErrorLogger');
|
|
4
3
|
|
|
5
4
|
class MasterActionFilters {
|
|
6
5
|
|
|
6
|
+
// Lazy-load master to avoid circular dependency (Google-style Singleton pattern)
|
|
7
|
+
static get _master() {
|
|
8
|
+
if (!MasterActionFilters.__masterCache) {
|
|
9
|
+
MasterActionFilters.__masterCache = require('./MasterControl');
|
|
10
|
+
}
|
|
11
|
+
return MasterActionFilters.__masterCache;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
constructor() {
|
|
8
15
|
// FIXED: Instance-level storage instead of module-level
|
|
9
16
|
// Each controller gets its own filter arrays
|
|
@@ -200,4 +207,12 @@ class MasterActionFilters {
|
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
|
|
203
|
-
|
|
210
|
+
// Export and lazy register (prevents circular dependency - Spring/Angular pattern)
|
|
211
|
+
module.exports = MasterActionFilters;
|
|
212
|
+
|
|
213
|
+
setImmediate(() => {
|
|
214
|
+
const master = require('./MasterControl');
|
|
215
|
+
if (master && master.extendController) {
|
|
216
|
+
master.extendController(MasterActionFilters);
|
|
217
|
+
}
|
|
218
|
+
});
|
package/MasterControl.js
CHANGED
|
@@ -319,21 +319,48 @@ class MasterControl {
|
|
|
319
319
|
'TemplateOverwrite': './TemplateOverwrite'
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
// Explicit module registration (prevents circular dependency issues)
|
|
323
|
+
// This is the Google-style dependency injection pattern
|
|
324
|
+
const moduleRegistry = {
|
|
325
|
+
'pipeline': { path: './MasterPipeline', exportName: 'MasterPipeline' },
|
|
326
|
+
'timeout': { path: './MasterTimeout', exportName: 'MasterTimeout' },
|
|
327
|
+
'errorRenderer': { path: './error/MasterErrorRenderer', exportName: 'MasterErrorRenderer' },
|
|
328
|
+
'error': { path: './error/MasterError', exportName: 'MasterError' },
|
|
329
|
+
'router': { path: './MasterRouter', exportName: 'MasterRouter' },
|
|
330
|
+
'request': { path: './MasterRequest', exportName: 'MasterRequest' },
|
|
331
|
+
'cors': { path: './MasterCors', exportName: 'MasterCors' },
|
|
332
|
+
'socket': { path: './MasterSocket', exportName: 'MasterSocket' },
|
|
333
|
+
'tempdata': { path: './MasterTemp', exportName: 'MasterTemp' },
|
|
334
|
+
'overwrite': { path: './TemplateOverwrite', exportName: 'TemplateOverwrite' },
|
|
335
|
+
'session': { path: './security/SessionSecurity', exportName: 'MasterSessionSecurity' }
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
for (const [name, config] of Object.entries(moduleRegistry)) {
|
|
323
339
|
try {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
340
|
+
const module = require(config.path);
|
|
341
|
+
const ClassConstructor = module[config.exportName] || module;
|
|
326
342
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
if (ClassConstructor) {
|
|
344
|
+
$that[name] = new ClassConstructor();
|
|
345
|
+
} else {
|
|
346
|
+
console.warn(`[MasterControl] Module ${name} does not export ${config.exportName}`);
|
|
330
347
|
}
|
|
331
|
-
// Most modules auto-register via master.extend() at module load time
|
|
332
348
|
} catch (e) {
|
|
333
|
-
console.error(`[MasterControl] Failed to load ${
|
|
349
|
+
console.error(`[MasterControl] Failed to load ${name}:`, e.message);
|
|
334
350
|
}
|
|
335
351
|
}
|
|
336
352
|
|
|
353
|
+
// Load view and controller extensions (these extend prototypes, not master instance)
|
|
354
|
+
try {
|
|
355
|
+
require('./MasterAction');
|
|
356
|
+
require('./MasterActionFilters');
|
|
357
|
+
require('./MasterHtml');
|
|
358
|
+
require('./MasterTemplate');
|
|
359
|
+
require('./MasterTools');
|
|
360
|
+
} catch (e) {
|
|
361
|
+
console.error('[MasterControl] Failed to load extensions:', e.message);
|
|
362
|
+
}
|
|
363
|
+
|
|
337
364
|
// Initialize global error handlers
|
|
338
365
|
setupGlobalErrorHandlers();
|
|
339
366
|
|
package/MasterCors.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// version 0.0.3 - robust origin handling (all envs), creds-safe reflection, function origins, extended Vary
|
|
2
|
-
var master = require('./MasterControl');
|
|
3
2
|
|
|
4
3
|
// todo - res.setHeader('Access-Control-Request-Method', '*');
|
|
5
4
|
class MasterCors{
|
|
@@ -198,4 +197,4 @@ class MasterCors{
|
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
|
|
200
|
+
module.exports = { MasterCors };
|
package/MasterHtml.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// version 0.0.25
|
|
2
2
|
|
|
3
|
-
var master = require('./MasterControl');
|
|
4
3
|
var fs = require('fs');
|
|
5
4
|
var tempClass = require('./MasterTemplate');
|
|
6
5
|
var toolClass = require('./MasterTools');
|
|
@@ -17,6 +16,14 @@ const { sanitizeTemplateHTML, sanitizeUserHTML, escapeHTML } = require('./securi
|
|
|
17
16
|
|
|
18
17
|
class html {
|
|
19
18
|
|
|
19
|
+
// Lazy-load master to avoid circular dependency (Google-style lazy initialization)
|
|
20
|
+
get _master() {
|
|
21
|
+
if (!this.__masterCache) {
|
|
22
|
+
this.__masterCache = require('./MasterControl');
|
|
23
|
+
}
|
|
24
|
+
return this.__masterCache;
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
javaScriptSerializer(name, obj){
|
|
21
28
|
// SECURITY: Escape closing script tags and dangerous characters
|
|
22
29
|
const jsonStr = JSON.stringify(obj)
|
|
@@ -630,5 +637,13 @@ class html {
|
|
|
630
637
|
|
|
631
638
|
}
|
|
632
639
|
|
|
633
|
-
|
|
640
|
+
// Export and lazy register (prevents circular dependency - Spring/Angular pattern)
|
|
641
|
+
module.exports = html;
|
|
642
|
+
|
|
643
|
+
setImmediate(() => {
|
|
644
|
+
const master = require('./MasterControl');
|
|
645
|
+
if (master && master.extendView) {
|
|
646
|
+
master.extendView("html", html);
|
|
647
|
+
}
|
|
648
|
+
});
|
|
634
649
|
|
package/MasterPipeline.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// MasterPipeline - Middleware Pipeline System
|
|
2
2
|
// version 1.0
|
|
3
3
|
|
|
4
|
-
var master = require('./MasterControl');
|
|
5
4
|
const { logger } = require('./error/MasterErrorLogger');
|
|
6
5
|
|
|
7
6
|
class MasterPipeline {
|
|
@@ -340,5 +339,5 @@ class MasterPipeline {
|
|
|
340
339
|
}
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
//
|
|
344
|
-
|
|
342
|
+
// Export for MasterControl to register (prevents circular dependency)
|
|
343
|
+
module.exports = { MasterPipeline };
|
package/MasterRequest.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
// version 0.0.2
|
|
3
3
|
|
|
4
|
-
var master = require('./MasterControl');
|
|
5
4
|
var url = require('url');
|
|
6
5
|
const StringDecoder = require('string_decoder').StringDecoder;
|
|
7
6
|
var qs = require('qs');
|
|
@@ -407,4 +406,4 @@ class MasterRequest{
|
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
408
|
|
|
410
|
-
|
|
409
|
+
module.exports = { MasterRequest };
|
package/MasterRouter.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// version 0.0.250
|
|
2
2
|
|
|
3
|
-
var master = require('./MasterControl');
|
|
4
3
|
var toolClass = require('./MasterTools');
|
|
5
4
|
const EventEmitter = require("events");
|
|
6
5
|
var path = require('path');
|
|
@@ -564,4 +563,4 @@ class MasterRouter {
|
|
|
564
563
|
|
|
565
564
|
}
|
|
566
565
|
|
|
567
|
-
|
|
566
|
+
module.exports = { MasterRouter };
|
package/MasterSocket.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// version 0.1.2
|
|
2
2
|
|
|
3
|
-
var master = require('./MasterControl');
|
|
4
3
|
const { Server } = require('socket.io');
|
|
5
4
|
const fs = require('fs');
|
|
6
5
|
const path = require('path');
|
|
@@ -139,7 +138,7 @@ class MasterSocket{
|
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
module.exports = { MasterSocket };
|
|
143
142
|
|
|
144
143
|
// shallow+deep merge helper
|
|
145
144
|
function isObject(item) {
|
package/MasterTemp.js
CHANGED
package/MasterTimeout.js
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* @version 1.0.0
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
var master = require('./MasterControl');
|
|
17
16
|
const { logger } = require('./error/MasterErrorLogger');
|
|
18
17
|
|
|
19
18
|
class MasterTimeout {
|
|
@@ -327,6 +326,5 @@ class MasterTimeout {
|
|
|
327
326
|
}
|
|
328
327
|
}
|
|
329
328
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
module.exports = MasterTimeout;
|
|
329
|
+
// Export for MasterControl to register (prevents circular dependency)
|
|
330
|
+
module.exports = { MasterTimeout };
|
package/TemplateOverwrite.js
CHANGED
package/error/MasterError.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
// version 1.0.21 - improved console/error logging with syntax error code frames
|
|
3
|
-
var master = require('../MasterControl');
|
|
4
3
|
var winston = require('winston');
|
|
5
4
|
var fileserver = require('fs');
|
|
6
5
|
const { request } = require('http');
|
|
@@ -174,7 +173,7 @@ class MasterError{
|
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
|
|
176
|
+
module.exports = { MasterError };
|
|
178
177
|
|
|
179
178
|
// ================ CODES YOU CAN USE ================ //
|
|
180
179
|
// ACCEPTED 202 Accepted
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* @version 1.0.0
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
var master = require('../MasterControl');
|
|
18
17
|
const fs = require('fs');
|
|
19
18
|
const path = require('path');
|
|
20
19
|
const { logger } = require('./MasterErrorLogger');
|
|
@@ -524,6 +523,6 @@ class MasterErrorRenderer {
|
|
|
524
523
|
}
|
|
525
524
|
}
|
|
526
525
|
|
|
527
|
-
|
|
526
|
+
module.exports = { MasterErrorRenderer };
|
|
528
527
|
|
|
529
528
|
module.exports = MasterErrorRenderer;
|
package/package.json
CHANGED
|
@@ -407,9 +407,6 @@ const SESSION_BEST_PRACTICES = {
|
|
|
407
407
|
}
|
|
408
408
|
};
|
|
409
409
|
|
|
410
|
-
// MasterController Integration
|
|
411
|
-
const master = require('../MasterControl');
|
|
412
|
-
|
|
413
410
|
// Create MasterController-compatible wrapper
|
|
414
411
|
class MasterSessionSecurity {
|
|
415
412
|
constructor() {
|
|
@@ -425,6 +422,9 @@ class MasterSessionSecurity {
|
|
|
425
422
|
this._options = options;
|
|
426
423
|
this._instance = new SessionSecurity(options);
|
|
427
424
|
|
|
425
|
+
// Lazy load master to avoid circular dependency
|
|
426
|
+
const master = require('../MasterControl');
|
|
427
|
+
|
|
428
428
|
// Auto-register with pipeline if available
|
|
429
429
|
if (master.pipeline) {
|
|
430
430
|
master.pipeline.use(this._instance.middleware());
|
package/SECURITY-FIXES-v1.3.2.md
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
## MasterController v1.3.2 - Security Fixes
|
|
2
|
-
|
|
3
|
-
**Release Date:** 2026-01-11
|
|
4
|
-
**Security Level:** ⚠️ CRITICAL - Immediate upgrade recommended
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Executive Summary
|
|
9
|
-
|
|
10
|
-
This release fixes **5 critical security vulnerabilities** and brings MasterController to industry-standard security levels matching Rails, ASP.NET Core, and Django.
|
|
11
|
-
|
|
12
|
-
### Critical Fixes
|
|
13
|
-
|
|
14
|
-
1. **XSS in ALL Form Helpers** - Now escaped by default
|
|
15
|
-
2. **Single Global Filter Bug** - Now supports multiple filters per controller
|
|
16
|
-
3. **Open Redirect in requireHTTPS** - Now uses configured hostname
|
|
17
|
-
4. **Path Traversal Vulnerabilities** - All file operations now validated
|
|
18
|
-
5. **Synchronous File I/O** - Proper error handling added
|
|
19
|
-
|
|
20
|
-
### New Feature: Automatic Security Enforcement
|
|
21
|
-
|
|
22
|
-
Security is now **enforced by default** with opt-in configuration:
|
|
23
|
-
- ✅ Auto-CSRF validation on POST/PUT/DELETE
|
|
24
|
-
- ✅ Auto-input sanitization
|
|
25
|
-
- ✅ Auto-HTTPS enforcement (production)
|
|
26
|
-
- ✅ Auto-security headers
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## What's Fixed
|
|
31
|
-
|
|
32
|
-
### 1. XSS Protection in Form Helpers (CRITICAL)
|
|
33
|
-
|
|
34
|
-
**Before (v1.3.1):**
|
|
35
|
-
```javascript
|
|
36
|
-
// ❌ VULNERABLE - No escaping
|
|
37
|
-
this.html.linkTo('<script>alert("XSS")</script>', '/page');
|
|
38
|
-
// Output: <a href=/page><script>alert("XSS")</script></a>
|
|
39
|
-
// XSS EXECUTED!
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**After (v1.3.2):**
|
|
43
|
-
```javascript
|
|
44
|
-
// ✅ SAFE - Auto-escaped
|
|
45
|
-
this.html.linkTo('<script>alert("XSS")</script>', '/page');
|
|
46
|
-
// Output: <a href="/page"><script>alert("XSS")</script></a>
|
|
47
|
-
// Safe to display!
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Fixed Methods:**
|
|
51
|
-
- ✅ `linkTo()` - Escapes name and URL
|
|
52
|
-
- ✅ `imgTag()` - Escapes alt and src
|
|
53
|
-
- ✅ `textFieldTag()` - Escapes all attributes
|
|
54
|
-
- ✅ `passwordFieldTag()` - Escapes all attributes
|
|
55
|
-
- ✅ `hiddenFieldTag()` - Escapes value and attributes
|
|
56
|
-
- ✅ `textAreaTag()` - Escapes content and attributes
|
|
57
|
-
- ✅ `submitButton()` - Escapes name and attributes
|
|
58
|
-
- ✅ All 15+ input field helpers
|
|
59
|
-
- ✅ `javaScriptSerializer()` - Escapes `</script>` tags
|
|
60
|
-
|
|
61
|
-
**Impact:** Prevents stored XSS, reflected XSS, DOM-based XSS attacks.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
### 2. Action Filter Architecture Fix (CRITICAL)
|
|
66
|
-
|
|
67
|
-
**Problem:** Only ONE filter could exist globally. Each new filter overwrote the previous one.
|
|
68
|
-
|
|
69
|
-
**Before (v1.3.1):**
|
|
70
|
-
```javascript
|
|
71
|
-
// UserController.js
|
|
72
|
-
class UserController {
|
|
73
|
-
constructor() {
|
|
74
|
-
// Register filter
|
|
75
|
-
this.beforeAction(['show'], () => {
|
|
76
|
-
console.log('User filter');
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// AdminController.js
|
|
82
|
-
class AdminController {
|
|
83
|
-
constructor() {
|
|
84
|
-
// ❌ BUG: This OVERWRITES UserController's filter!
|
|
85
|
-
this.beforeAction(['dashboard'], () => {
|
|
86
|
-
console.log('Admin filter');
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Result: UserController has NO filter anymore!
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**After (v1.3.2):**
|
|
95
|
-
```javascript
|
|
96
|
-
// ✅ FIXED: Each controller has independent filters
|
|
97
|
-
class UserController {
|
|
98
|
-
constructor() {
|
|
99
|
-
this.beforeAction(['show'], () => console.log('User filter 1'));
|
|
100
|
-
this.beforeAction(['show'], () => console.log('User filter 2'));
|
|
101
|
-
this.beforeAction(['edit'], () => console.log('Edit filter'));
|
|
102
|
-
// All 3 filters coexist!
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
class AdminController {
|
|
107
|
-
constructor() {
|
|
108
|
-
this.beforeAction(['dashboard'], () => console.log('Admin filter'));
|
|
109
|
-
// Independent from UserController
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**New Features:**
|
|
115
|
-
- ✅ Instance-level filters (not global)
|
|
116
|
-
- ✅ Multiple filters per controller
|
|
117
|
-
- ✅ Async/await support
|
|
118
|
-
- ✅ Error handling with try/catch
|
|
119
|
-
- ✅ Timeout protection (5 seconds default)
|
|
120
|
-
- ✅ No variable shadowing bugs
|
|
121
|
-
- ✅ No race conditions
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
### 3. Open Redirect Fix (CRITICAL)
|
|
126
|
-
|
|
127
|
-
**Problem:** `requireHTTPS()` used unvalidated `Host` header, allowing phishing attacks.
|
|
128
|
-
|
|
129
|
-
**Before (v1.3.1):**
|
|
130
|
-
```javascript
|
|
131
|
-
// ❌ VULNERABLE
|
|
132
|
-
requireHTTPS() {
|
|
133
|
-
const httpsUrl = `https://${this.__requestObject.request.headers.host}${this.__requestObject.pathName}`;
|
|
134
|
-
this.redirectTo(httpsUrl);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Attack:
|
|
138
|
-
// HTTP Request with: Host: evil.com
|
|
139
|
-
// Redirects to: https://evil.com/login
|
|
140
|
-
// User enters credentials on attacker's phishing site!
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
**After (v1.3.2):**
|
|
144
|
-
```javascript
|
|
145
|
-
// ✅ FIXED - Uses configured hostname
|
|
146
|
-
requireHTTPS() {
|
|
147
|
-
const configuredHost = master.env.server.hostname; // From config
|
|
148
|
-
const httpsUrl = `https://${configuredHost}${this.__requestObject.pathName}`;
|
|
149
|
-
this.redirectTo(httpsUrl);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Attack fails:
|
|
153
|
-
// HTTP Request with: Host: evil.com
|
|
154
|
-
// Redirects to: https://legitimate.com/login (from config)
|
|
155
|
-
// User goes to correct site!
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Configuration Required:**
|
|
159
|
-
```javascript
|
|
160
|
-
// config/environments/env.production.json
|
|
161
|
-
{
|
|
162
|
-
"server": {
|
|
163
|
-
"hostname": "yourapp.com",
|
|
164
|
-
"httpsPort": 443
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
### 4. Path Traversal Protection (HIGH)
|
|
172
|
-
|
|
173
|
-
**Fixed Methods:**
|
|
174
|
-
- ✅ `returnPartialView()` - Validates path, prevents `../`
|
|
175
|
-
- ✅ `returnViewWithoutEngine()` - Validates path
|
|
176
|
-
- ✅ `renderPartial()` - Validates path
|
|
177
|
-
- ✅ `renderStyles()` - Validates folder name
|
|
178
|
-
- ✅ `renderScripts()` - Validates folder name
|
|
179
|
-
|
|
180
|
-
**Before (v1.3.1):**
|
|
181
|
-
```javascript
|
|
182
|
-
// ❌ VULNERABLE
|
|
183
|
-
this.returnPartialView('../../../../etc/passwd');
|
|
184
|
-
// Reads /app/root/../../../../etc/passwd
|
|
185
|
-
// System file exposed!
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**After (v1.3.2):**
|
|
189
|
-
```javascript
|
|
190
|
-
// ✅ PROTECTED
|
|
191
|
-
this.returnPartialView('../../../../etc/passwd');
|
|
192
|
-
// Blocked! Returns error
|
|
193
|
-
// Logs security warning
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
### 5. Other Fixes
|
|
199
|
-
|
|
200
|
-
**Undefined Variables:**
|
|
201
|
-
- ✅ Fixed `redirectToAction()` - `resp` and `req` now properly defined
|
|
202
|
-
|
|
203
|
-
**Error Handling:**
|
|
204
|
-
- ✅ `returnJson()` - Try/catch added, checks both `_headerSent` and `headersSent`
|
|
205
|
-
- ✅ `returnPartialView()` - Try/catch for file operations
|
|
206
|
-
- ✅ `returnViewWithoutEngine()` - Try/catch for file operations
|
|
207
|
-
|
|
208
|
-
**JSON Serialization:**
|
|
209
|
-
- ✅ `javaScriptSerializer()` - Escapes `</script>`, `<`, `>`, `&` characters
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## New Feature: Automatic Security Enforcement
|
|
214
|
-
|
|
215
|
-
### What is Security Enforcement?
|
|
216
|
-
|
|
217
|
-
Instead of developers manually calling security methods, MasterController now **automatically enforces security** for all requests.
|
|
218
|
-
|
|
219
|
-
### Enable Security Enforcement
|
|
220
|
-
|
|
221
|
-
**Step 1: Edit `config/initializers/config.js`:**
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
const SecurityEnforcement = require('mastercontroller/security/SecurityEnforcement');
|
|
225
|
-
|
|
226
|
-
// Initialize security enforcement
|
|
227
|
-
const securityConfig = SecurityEnforcement.init({
|
|
228
|
-
csrf: true, // Auto-validate CSRF on POST/PUT/DELETE
|
|
229
|
-
sanitizeInputs: true, // Auto-sanitize all inputs
|
|
230
|
-
httpsOnly: true, // Require HTTPS in production
|
|
231
|
-
autoEscape: true, // Auto-escape template output (future)
|
|
232
|
-
csrfExcludePaths: [ // Paths that don't need CSRF (webhooks)
|
|
233
|
-
'/api/webhook'
|
|
234
|
-
]
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// Register enforcement middleware (IMPORTANT!)
|
|
238
|
-
master.pipeline.use(SecurityEnforcement.middleware(securityConfig));
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### What It Does Automatically
|
|
242
|
-
|
|
243
|
-
#### 1. CSRF Protection
|
|
244
|
-
|
|
245
|
-
**Automatic CSRF validation on all POST/PUT/DELETE/PATCH requests:**
|
|
246
|
-
|
|
247
|
-
```javascript
|
|
248
|
-
// NO CODE CHANGES NEEDED IN CONTROLLERS!
|
|
249
|
-
|
|
250
|
-
// Before (required manual check):
|
|
251
|
-
class UsersController {
|
|
252
|
-
create(obj) {
|
|
253
|
-
// ❌ Developer must remember to check CSRF
|
|
254
|
-
if (!this.validateCSRF()) {
|
|
255
|
-
return this.returnError(403, 'CSRF invalid');
|
|
256
|
-
}
|
|
257
|
-
// ... create user
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// After (automatic):
|
|
262
|
-
class UsersController {
|
|
263
|
-
create(obj) {
|
|
264
|
-
// ✅ CSRF already validated by middleware!
|
|
265
|
-
// Just handle the request
|
|
266
|
-
const user = this.userContext.create(obj.params.formData);
|
|
267
|
-
this.json({ user });
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
**How to Include CSRF Token in Forms:**
|
|
273
|
-
|
|
274
|
-
```html
|
|
275
|
-
<!-- HTML Form -->
|
|
276
|
-
<form method="POST" action="/users">
|
|
277
|
-
<input type="hidden" name="_csrf" value="<%= this.generateCSRFToken() %>">
|
|
278
|
-
<input type="text" name="username">
|
|
279
|
-
<button type="submit">Create</button>
|
|
280
|
-
</form>
|
|
281
|
-
|
|
282
|
-
<!-- AJAX Request -->
|
|
283
|
-
<script>
|
|
284
|
-
fetch('/users', {
|
|
285
|
-
method: 'POST',
|
|
286
|
-
headers: {
|
|
287
|
-
'Content-Type': 'application/json',
|
|
288
|
-
'X-CSRF-Token': '<%= this.generateCSRFToken() %>'
|
|
289
|
-
},
|
|
290
|
-
body: JSON.stringify({ username: 'john' })
|
|
291
|
-
});
|
|
292
|
-
</script>
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
#### 2. Input Sanitization
|
|
296
|
-
|
|
297
|
-
**All inputs automatically sanitized to prevent XSS:**
|
|
298
|
-
|
|
299
|
-
```javascript
|
|
300
|
-
// Before:
|
|
301
|
-
class PostsController {
|
|
302
|
-
create(obj) {
|
|
303
|
-
// ❌ Must manually sanitize
|
|
304
|
-
const title = this.sanitizeInput(obj.params.formData.title);
|
|
305
|
-
const body = this.sanitizeInput(obj.params.formData.body);
|
|
306
|
-
// ... create post
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// After:
|
|
311
|
-
class PostsController {
|
|
312
|
-
create(obj) {
|
|
313
|
-
// ✅ Already sanitized!
|
|
314
|
-
const title = obj.params.formData.title; // Safe
|
|
315
|
-
const body = obj.params.formData.body; // Safe
|
|
316
|
-
// ... create post
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### 3. HTTPS Enforcement (Production)
|
|
322
|
-
|
|
323
|
-
**Automatic HTTPS redirect in production:**
|
|
324
|
-
|
|
325
|
-
```javascript
|
|
326
|
-
// NO CODE CHANGES NEEDED!
|
|
327
|
-
|
|
328
|
-
// Before:
|
|
329
|
-
class AdminController {
|
|
330
|
-
dashboard(obj) {
|
|
331
|
-
// ❌ Must manually check HTTPS
|
|
332
|
-
if (!this.requireHTTPS()) return;
|
|
333
|
-
// ... render dashboard
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// After:
|
|
338
|
-
class AdminController {
|
|
339
|
-
dashboard(obj) {
|
|
340
|
-
// ✅ Already on HTTPS! (auto-redirected)
|
|
341
|
-
// ... render dashboard
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
#### 4. Security Headers
|
|
347
|
-
|
|
348
|
-
**Automatic security headers on all responses:**
|
|
349
|
-
|
|
350
|
-
Headers applied automatically:
|
|
351
|
-
- ✅ `X-XSS-Protection: 1; mode=block`
|
|
352
|
-
- ✅ `X-Frame-Options: SAMEORIGIN` (clickjacking protection)
|
|
353
|
-
- ✅ `X-Content-Type-Options: nosniff` (MIME sniffing protection)
|
|
354
|
-
- ✅ `Referrer-Policy: strict-origin-when-cross-origin`
|
|
355
|
-
- ✅ `Content-Security-Policy: default-src 'self'`
|
|
356
|
-
- ✅ `Permissions-Policy: geolocation=(), microphone=(), camera=()`
|
|
357
|
-
|
|
358
|
-
---
|
|
359
|
-
|
|
360
|
-
## Migration Guide
|
|
361
|
-
|
|
362
|
-
### For v1.3.1 Users
|
|
363
|
-
|
|
364
|
-
**Good News:** Most changes are backward compatible!
|
|
365
|
-
|
|
366
|
-
#### Required Changes
|
|
367
|
-
|
|
368
|
-
**1. Configure Hostname (for requireHTTPS):**
|
|
369
|
-
|
|
370
|
-
```json
|
|
371
|
-
// config/environments/env.production.json
|
|
372
|
-
{
|
|
373
|
-
"server": {
|
|
374
|
-
"hostname": "yourapp.com",
|
|
375
|
-
"httpsPort": 443
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
**2. Enable Security Enforcement (Recommended):**
|
|
381
|
-
|
|
382
|
-
```javascript
|
|
383
|
-
// config/initializers/config.js
|
|
384
|
-
const SecurityEnforcement = require('mastercontroller/security/SecurityEnforcement');
|
|
385
|
-
|
|
386
|
-
const securityConfig = SecurityEnforcement.init({
|
|
387
|
-
csrf: true,
|
|
388
|
-
sanitizeInputs: true,
|
|
389
|
-
httpsOnly: true
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
master.pipeline.use(SecurityEnforcement.middleware(securityConfig));
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
#### Optional Changes
|
|
396
|
-
|
|
397
|
-
**Remove Manual Security Checks:**
|
|
398
|
-
|
|
399
|
-
You can now remove manual security checks since they're automatic:
|
|
400
|
-
|
|
401
|
-
```javascript
|
|
402
|
-
// Before (manual):
|
|
403
|
-
class UsersController {
|
|
404
|
-
create(obj) {
|
|
405
|
-
// Can remove these now:
|
|
406
|
-
if (!this.validateCSRF()) return this.returnError(403, 'CSRF invalid');
|
|
407
|
-
obj.params.formData = this.sanitizeInput(obj.params.formData);
|
|
408
|
-
if (!this.requireHTTPS()) return;
|
|
409
|
-
|
|
410
|
-
// ... business logic
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// After (automatic):
|
|
415
|
-
class UsersController {
|
|
416
|
-
create(obj) {
|
|
417
|
-
// All security checks done by middleware!
|
|
418
|
-
// ... business logic
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
## Testing Your Upgrade
|
|
426
|
-
|
|
427
|
-
### 1. XSS Protection Test
|
|
428
|
-
|
|
429
|
-
```javascript
|
|
430
|
-
// Test that XSS is blocked
|
|
431
|
-
const html = master.viewList.html;
|
|
432
|
-
const result = html.linkTo('<script>alert("XSS")</script>', '/test');
|
|
433
|
-
|
|
434
|
-
console.log(result);
|
|
435
|
-
// Should output: <a href="/test"><script>alert("XSS")</script></a>
|
|
436
|
-
// NOT: <a href=/test><script>alert("XSS")</script></a>
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### 2. Action Filter Test
|
|
440
|
-
|
|
441
|
-
```javascript
|
|
442
|
-
// Test multiple filters
|
|
443
|
-
class TestController {
|
|
444
|
-
constructor() {
|
|
445
|
-
this.beforeAction(['show'], () => console.log('Filter 1'));
|
|
446
|
-
this.beforeAction(['show'], () => console.log('Filter 2'));
|
|
447
|
-
this.beforeAction(['edit'], () => console.log('Filter 3'));
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const controller = new TestController();
|
|
452
|
-
console.log(controller._beforeActionFilters.length);
|
|
453
|
-
// Should output: 3 (all filters registered)
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### 3. Path Traversal Test
|
|
457
|
-
|
|
458
|
-
```javascript
|
|
459
|
-
// Test path traversal protection
|
|
460
|
-
const result = this.returnPartialView('../../../../etc/passwd', {});
|
|
461
|
-
// Should return error, NOT read file
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### 4. Open Redirect Test
|
|
465
|
-
|
|
466
|
-
```http
|
|
467
|
-
GET / HTTP/1.1
|
|
468
|
-
Host: evil.com
|
|
469
|
-
|
|
470
|
-
# Should redirect to configured hostname, not evil.com
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### 5. CSRF Test
|
|
474
|
-
|
|
475
|
-
```bash
|
|
476
|
-
# Without CSRF token (should fail)
|
|
477
|
-
curl -X POST http://localhost:3000/users \
|
|
478
|
-
-H "Content-Type: application/json" \
|
|
479
|
-
-d '{"username":"test"}'
|
|
480
|
-
|
|
481
|
-
# Should return: 403 Forbidden
|
|
482
|
-
|
|
483
|
-
# With CSRF token (should succeed)
|
|
484
|
-
curl -X POST http://localhost:3000/users \
|
|
485
|
-
-H "Content-Type: application/json" \
|
|
486
|
-
-H "X-CSRF-Token: <valid-token>" \
|
|
487
|
-
-d '{"username":"test"}'
|
|
488
|
-
|
|
489
|
-
# Should return: 200 OK
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
---
|
|
493
|
-
|
|
494
|
-
## Security Comparison: Before vs After
|
|
495
|
-
|
|
496
|
-
### XSS Protection
|
|
497
|
-
|
|
498
|
-
| Feature | v1.3.1 | v1.3.2 |
|
|
499
|
-
|---------|--------|--------|
|
|
500
|
-
| Auto-escape form helpers | ❌ No | ✅ Yes |
|
|
501
|
-
| Attribute quoting | ❌ No | ✅ Yes |
|
|
502
|
-
| Script tag escape in JSON | ❌ No | ✅ Yes |
|
|
503
|
-
| Auto-sanitize inputs | ❌ Manual | ✅ Automatic (opt-in) |
|
|
504
|
-
|
|
505
|
-
### CSRF Protection
|
|
506
|
-
|
|
507
|
-
| Feature | v1.3.1 | v1.3.2 |
|
|
508
|
-
|---------|--------|--------|
|
|
509
|
-
| Token generation | ✅ Yes | ✅ Yes |
|
|
510
|
-
| Auto-validation | ❌ Manual | ✅ Automatic (opt-in) |
|
|
511
|
-
| Exclude paths | ❌ No | ✅ Yes |
|
|
512
|
-
|
|
513
|
-
### Action Filters
|
|
514
|
-
|
|
515
|
-
| Feature | v1.3.1 | v1.3.2 |
|
|
516
|
-
|---------|--------|--------|
|
|
517
|
-
| Multiple filters | ❌ No (1 only) | ✅ Yes (unlimited) |
|
|
518
|
-
| Instance-level | ❌ No (global) | ✅ Yes |
|
|
519
|
-
| Async support | ❌ No | ✅ Yes |
|
|
520
|
-
| Error handling | ❌ No | ✅ Yes |
|
|
521
|
-
| Timeout protection | ❌ No | ✅ Yes |
|
|
522
|
-
|
|
523
|
-
### Path Security
|
|
524
|
-
|
|
525
|
-
| Feature | v1.3.1 | v1.3.2 |
|
|
526
|
-
|---------|--------|--------|
|
|
527
|
-
| Path traversal protection | ❌ No | ✅ Yes |
|
|
528
|
-
| Path validation | ❌ No | ✅ Yes |
|
|
529
|
-
| Dotfile blocking | ❌ No | ✅ Yes |
|
|
530
|
-
|
|
531
|
-
### HTTPS
|
|
532
|
-
|
|
533
|
-
| Feature | v1.3.1 | v1.3.2 |
|
|
534
|
-
|---------|--------|--------|
|
|
535
|
-
| HTTPS redirect | ⚠️ Vulnerable | ✅ Secure |
|
|
536
|
-
| Uses Host header | ❌ Yes | ✅ No |
|
|
537
|
-
| Uses config hostname | ❌ No | ✅ Yes |
|
|
538
|
-
| Auto-enforcement | ❌ No | ✅ Yes (opt-in) |
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
## Industry Standards Compliance
|
|
543
|
-
|
|
544
|
-
### ✅ Now Matches Rails
|
|
545
|
-
|
|
546
|
-
- ✅ Auto-escape output
|
|
547
|
-
- ✅ CSRF protection built-in
|
|
548
|
-
- ✅ Multiple filter chains
|
|
549
|
-
- ✅ Path security
|
|
550
|
-
- ✅ XSS protection
|
|
551
|
-
|
|
552
|
-
### ✅ Now Matches ASP.NET Core
|
|
553
|
-
|
|
554
|
-
- ✅ Auto-HTML encoding
|
|
555
|
-
- ✅ Anti-forgery tokens
|
|
556
|
-
- ✅ Multiple filter attributes
|
|
557
|
-
- ✅ Async filters
|
|
558
|
-
- ✅ HTTPS enforcement
|
|
559
|
-
|
|
560
|
-
### ✅ Now Matches Django
|
|
561
|
-
|
|
562
|
-
- ✅ Auto-escaping templates
|
|
563
|
-
- ✅ CSRF middleware
|
|
564
|
-
- ✅ Multiple decorators
|
|
565
|
-
- ✅ Input validation
|
|
566
|
-
- ✅ XSS protection
|
|
567
|
-
|
|
568
|
-
---
|
|
569
|
-
|
|
570
|
-
## Breaking Changes
|
|
571
|
-
|
|
572
|
-
### None!
|
|
573
|
-
|
|
574
|
-
All changes are backward compatible. Existing code will continue to work.
|
|
575
|
-
|
|
576
|
-
**Recommendations:**
|
|
577
|
-
1. Enable security enforcement for new protection
|
|
578
|
-
2. Configure hostname for requireHTTPS
|
|
579
|
-
3. Remove manual security checks (now redundant)
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
## Performance Impact
|
|
584
|
-
|
|
585
|
-
**Minimal:** <1ms per request
|
|
586
|
-
|
|
587
|
-
- Form helpers: Same performance (now safer)
|
|
588
|
-
- Filters: Slightly faster (better architecture)
|
|
589
|
-
- Security enforcement: ~0.5ms overhead
|
|
590
|
-
- Path validation: <0.1ms overhead
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
## Credits
|
|
595
|
-
|
|
596
|
-
Security audit and fixes by Claude Code, based on industry standards from:
|
|
597
|
-
- Ruby on Rails (ActionView, ActionController)
|
|
598
|
-
- ASP.NET Core (Razor, MVC)
|
|
599
|
-
- Django (Templates, Middleware)
|
|
600
|
-
- OWASP Top 10 (2021)
|
|
601
|
-
|
|
602
|
-
---
|
|
603
|
-
|
|
604
|
-
## Support
|
|
605
|
-
|
|
606
|
-
**Issues:** https://github.com/alexanderrich/MasterController/issues
|
|
607
|
-
**Documentation:** See README.md
|
|
608
|
-
**Security:** Report security issues to security@mastercontroller.com
|
|
609
|
-
|
|
610
|
-
---
|
|
611
|
-
|
|
612
|
-
**Upgrade Now:** `npm install mastercontroller@latest`
|
|
613
|
-
|
|
614
|
-
**MasterController v1.3.2 - Production-Ready Security**
|