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
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
# MasterController HTTPS/TLS Security Audit
|
|
2
|
+
|
|
3
|
+
**Date:** January 2026
|
|
4
|
+
**Version:** v1.3.1
|
|
5
|
+
**Auditor:** Security Review
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Executive Summary
|
|
10
|
+
|
|
11
|
+
**Overall Security Rating:** ⚠️ **GOOD with Critical Issues**
|
|
12
|
+
|
|
13
|
+
MasterController's HTTPS/TLS implementation includes many advanced features but has several **critical security issues** that must be fixed before production deployment.
|
|
14
|
+
|
|
15
|
+
**✅ Strengths:**
|
|
16
|
+
- SNI (Server Name Indication) support with multiple domains
|
|
17
|
+
- TLS certificate live reload (zero-downtime updates)
|
|
18
|
+
- Secure TLS defaults (TLS 1.2+, cipher order)
|
|
19
|
+
- HTTP to HTTPS redirect server
|
|
20
|
+
- HSTS support with configurable max-age
|
|
21
|
+
- Security headers middleware
|
|
22
|
+
|
|
23
|
+
**❌ Critical Issues:**
|
|
24
|
+
1. Missing `enableHSTS()` method (documented but not implemented)
|
|
25
|
+
2. HSTS hardcoded max-age doesn't use configured value
|
|
26
|
+
3. Weak TLS minimum version (should be TLS 1.3 in 2026)
|
|
27
|
+
4. No cipher suite configuration by default
|
|
28
|
+
5. HTTP redirect doesn't validate host header (open redirect vulnerability)
|
|
29
|
+
6. Static file serving has path traversal vulnerability
|
|
30
|
+
7. Error responses leak stack traces in production
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Issue #1: Missing `enableHSTS()` Method
|
|
35
|
+
|
|
36
|
+
**Severity:** 🔴 **CRITICAL**
|
|
37
|
+
|
|
38
|
+
### Current State
|
|
39
|
+
```javascript
|
|
40
|
+
// README.md documents this:
|
|
41
|
+
master.enableHSTS(); // In production HTTPS
|
|
42
|
+
|
|
43
|
+
// But the method doesn't exist in MasterControl.js!
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Grep Result:**
|
|
47
|
+
```bash
|
|
48
|
+
$ grep -n "enableHSTS\s*(" MasterControl.js
|
|
49
|
+
(no results - method doesn't exist)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Issue
|
|
53
|
+
The README documents `master.enableHSTS()` as an API method, but it's not implemented. Users following the docs will get `TypeError: master.enableHSTS is not a function`.
|
|
54
|
+
|
|
55
|
+
### Fix Required
|
|
56
|
+
Add the missing method:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
// MasterControl.js
|
|
60
|
+
enableHSTS(maxAge = 31536000, includeSubDomains = true, preload = false) {
|
|
61
|
+
this._hstsEnabled = true;
|
|
62
|
+
this._hstsMaxAge = maxAge;
|
|
63
|
+
this._hstsIncludeSubDomains = includeSubDomains;
|
|
64
|
+
this._hstsPreload = preload;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
And update HSTS middleware to use these values:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// _registerCoreMiddleware() - line 546
|
|
72
|
+
$that.pipeline.use(async (ctx, next) => {
|
|
73
|
+
if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
|
|
74
|
+
let hstsValue = `max-age=${$that._hstsMaxAge || 31536000}`;
|
|
75
|
+
if ($that._hstsIncludeSubDomains) hstsValue += '; includeSubDomains';
|
|
76
|
+
if ($that._hstsPreload) hstsValue += '; preload';
|
|
77
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
78
|
+
}
|
|
79
|
+
await next();
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Issue #2: HSTS Max-Age Ignored
|
|
86
|
+
|
|
87
|
+
**Severity:** 🟡 **MEDIUM**
|
|
88
|
+
|
|
89
|
+
### Current State
|
|
90
|
+
```javascript
|
|
91
|
+
// MasterControl.js:416 - TLS config sets _hstsMaxAge
|
|
92
|
+
this._hstsMaxAge = tlsCfg.hstsMaxAge || 15552000; // 180 days
|
|
93
|
+
|
|
94
|
+
// MasterControl.js:547 - But middleware ignores it!
|
|
95
|
+
ctx.response.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
96
|
+
// ^^^ Hardcoded to 365 days!
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Issue
|
|
100
|
+
Users configure HSTS max-age in their environment config, but it's ignored. The middleware always uses 31536000 (365 days).
|
|
101
|
+
|
|
102
|
+
### Fix Required
|
|
103
|
+
Use the configured value:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
let hstsValue = `max-age=${$that._hstsMaxAge || 31536000}`;
|
|
107
|
+
if ($that._hstsIncludeSubDomains !== false) hstsValue += '; includeSubDomains';
|
|
108
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Issue #3: Weak TLS Minimum Version
|
|
114
|
+
|
|
115
|
+
**Severity:** 🟡 **MEDIUM** (but will become CRITICAL in 2026)
|
|
116
|
+
|
|
117
|
+
### Current State
|
|
118
|
+
```javascript
|
|
119
|
+
// MasterControl.js:327 - Default to TLS 1.2
|
|
120
|
+
if(!credentials.minVersion){ credentials.minVersion = 'TLSv1.2'; }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Comparison with Other Frameworks
|
|
124
|
+
|
|
125
|
+
| Framework | Default TLS Version (2026) | Recommendation |
|
|
126
|
+
|-----------|----------------------------|----------------|
|
|
127
|
+
| **Express** | Node.js default (TLS 1.2+) | TLS 1.2 minimum |
|
|
128
|
+
| **ASP.NET Core 8** | TLS 1.2 minimum | TLS 1.3 recommended |
|
|
129
|
+
| **Rails 7.1** | TLS 1.2 minimum | TLS 1.3 recommended |
|
|
130
|
+
| **Django 5.0** | TLS 1.2 minimum | TLS 1.3 recommended |
|
|
131
|
+
| **MasterController** | TLS 1.2 default | ⚠️ Should be TLS 1.3 |
|
|
132
|
+
|
|
133
|
+
### Industry Standards (2026)
|
|
134
|
+
- **PCI DSS 4.0:** Requires TLS 1.2+ (TLS 1.3 recommended)
|
|
135
|
+
- **NIST SP 800-52 Rev. 2:** Recommends TLS 1.3
|
|
136
|
+
- **OWASP:** TLS 1.3 recommended, TLS 1.2 acceptable
|
|
137
|
+
- **Mozilla SSL Config Generator:** TLS 1.3 for "Modern" config
|
|
138
|
+
|
|
139
|
+
### Fix Required
|
|
140
|
+
Update default to TLS 1.3 with fallback to TLS 1.2:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// MasterControl.js:327
|
|
144
|
+
if(!credentials.minVersion){
|
|
145
|
+
credentials.minVersion = 'TLSv1.3'; // Default to TLS 1.3
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Or make it configurable by environment:
|
|
149
|
+
if(!credentials.minVersion){
|
|
150
|
+
const isProduction = this.environmentType === 'production';
|
|
151
|
+
credentials.minVersion = isProduction ? 'TLSv1.3' : 'TLSv1.2';
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Why TLS 1.3?**
|
|
156
|
+
- Faster handshake (1-RTT vs 2-RTT)
|
|
157
|
+
- Forward secrecy by default
|
|
158
|
+
- Removed weak ciphers
|
|
159
|
+
- 0-RTT resumption (performance)
|
|
160
|
+
- Better privacy (encrypted SNI)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Issue #4: No Cipher Suite Configuration
|
|
165
|
+
|
|
166
|
+
**Severity:** 🟡 **MEDIUM**
|
|
167
|
+
|
|
168
|
+
### Current State
|
|
169
|
+
```javascript
|
|
170
|
+
// MasterControl.js:411 - Only set if provided in env
|
|
171
|
+
if(tlsCfg.ciphers){ options.ciphers = tlsCfg.ciphers; }
|
|
172
|
+
// Otherwise uses Node.js defaults (which may include weak ciphers)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Comparison with Other Frameworks
|
|
176
|
+
|
|
177
|
+
**Express (with helmet):**
|
|
178
|
+
```javascript
|
|
179
|
+
const helmet = require('helmet');
|
|
180
|
+
app.use(helmet.hsts({
|
|
181
|
+
maxAge: 31536000,
|
|
182
|
+
includeSubDomains: true,
|
|
183
|
+
preload: true
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
// Cipher suite must be set manually
|
|
187
|
+
const server = https.createServer({
|
|
188
|
+
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:...',
|
|
189
|
+
honorCipherOrder: true
|
|
190
|
+
}, app);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**ASP.NET Core 8:**
|
|
194
|
+
```csharp
|
|
195
|
+
// appsettings.json
|
|
196
|
+
{
|
|
197
|
+
"Kestrel": {
|
|
198
|
+
"EndpointDefaults": {
|
|
199
|
+
"Protocols": "Http1AndHttp2AndHttp3",
|
|
200
|
+
"SslProtocols": ["Tls13", "Tls12"],
|
|
201
|
+
"CipherSuitesPolicy": {
|
|
202
|
+
"CipherSuites": [
|
|
203
|
+
"TLS_AES_256_GCM_SHA384",
|
|
204
|
+
"TLS_CHACHA20_POLY1305_SHA256",
|
|
205
|
+
"TLS_AES_128_GCM_SHA256"
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Rails 7.1 (with Puma):**
|
|
214
|
+
```ruby
|
|
215
|
+
# config/puma.rb
|
|
216
|
+
ssl_bind '0.0.0.0', '443', {
|
|
217
|
+
key: 'key.pem',
|
|
218
|
+
cert: 'cert.pem',
|
|
219
|
+
ssl_cipher_filter: 'ECDHE-RSA-AES256-GCM-SHA384:...',
|
|
220
|
+
verify_mode: 'none'
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Mozilla SSL Configuration Levels
|
|
225
|
+
|
|
226
|
+
**Modern (2026 recommended):**
|
|
227
|
+
```javascript
|
|
228
|
+
ciphers: [
|
|
229
|
+
'TLS_AES_256_GCM_SHA384',
|
|
230
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
231
|
+
'TLS_AES_128_GCM_SHA256'
|
|
232
|
+
].join(':')
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Intermediate (broader compatibility):**
|
|
236
|
+
```javascript
|
|
237
|
+
ciphers: [
|
|
238
|
+
'TLS_AES_256_GCM_SHA384',
|
|
239
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
240
|
+
'TLS_AES_128_GCM_SHA256',
|
|
241
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
242
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
243
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
244
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
245
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
246
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
247
|
+
].join(':')
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Fix Required
|
|
251
|
+
|
|
252
|
+
Add secure defaults:
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// MasterControl.js:327 - After minVersion
|
|
256
|
+
if(!credentials.ciphers){
|
|
257
|
+
// Mozilla Intermediate config (2026)
|
|
258
|
+
credentials.ciphers = [
|
|
259
|
+
// TLS 1.3 ciphers
|
|
260
|
+
'TLS_AES_256_GCM_SHA384',
|
|
261
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
262
|
+
'TLS_AES_128_GCM_SHA256',
|
|
263
|
+
// TLS 1.2 ciphers (backward compatibility)
|
|
264
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
265
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
266
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
267
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
268
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
269
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
270
|
+
].join(':');
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Issue #5: HTTP to HTTPS Redirect - Open Redirect Vulnerability
|
|
277
|
+
|
|
278
|
+
**Severity:** 🔴 **CRITICAL**
|
|
279
|
+
|
|
280
|
+
### Current State
|
|
281
|
+
```javascript
|
|
282
|
+
// MasterControl.js:348-357
|
|
283
|
+
startHttpToHttpsRedirect(redirectPort, bindHost){
|
|
284
|
+
var $that = this;
|
|
285
|
+
return http.createServer(function (req, res) {
|
|
286
|
+
try{
|
|
287
|
+
var host = req.headers['host'] || '';
|
|
288
|
+
// Force original host, just change scheme
|
|
289
|
+
var location = 'https://' + host + req.url;
|
|
290
|
+
res.statusCode = 301;
|
|
291
|
+
res.setHeader('Location', location);
|
|
292
|
+
res.end();
|
|
293
|
+
}catch(e){
|
|
294
|
+
res.statusCode = 500;
|
|
295
|
+
res.end();
|
|
296
|
+
}
|
|
297
|
+
}).listen(redirectPort, bindHost);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Vulnerability
|
|
302
|
+
The `host` header is user-controlled and not validated. An attacker can exploit this:
|
|
303
|
+
|
|
304
|
+
**Attack Example:**
|
|
305
|
+
```bash
|
|
306
|
+
# Attacker crafts malicious request
|
|
307
|
+
curl -H "Host: evil.com" http://example.com/login
|
|
308
|
+
|
|
309
|
+
# Server redirects to:
|
|
310
|
+
Location: https://evil.com/login
|
|
311
|
+
# User credentials sent to attacker's domain!
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Comparison with Other Frameworks
|
|
315
|
+
|
|
316
|
+
**Express (with express-force-ssl):**
|
|
317
|
+
```javascript
|
|
318
|
+
// Validates host against allowed list
|
|
319
|
+
const allowedHosts = ['example.com', 'www.example.com'];
|
|
320
|
+
app.use((req, res, next) => {
|
|
321
|
+
const host = req.hostname;
|
|
322
|
+
if (!allowedHosts.includes(host)) {
|
|
323
|
+
return res.status(400).send('Invalid host');
|
|
324
|
+
}
|
|
325
|
+
if (!req.secure) {
|
|
326
|
+
return res.redirect(301, `https://${host}${req.url}`);
|
|
327
|
+
}
|
|
328
|
+
next();
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**ASP.NET Core 8:**
|
|
333
|
+
```csharp
|
|
334
|
+
// Built-in HTTPS redirection validates hostname
|
|
335
|
+
app.UseHttpsRedirection();
|
|
336
|
+
|
|
337
|
+
// Or manual validation
|
|
338
|
+
app.Use(async (context, next) => {
|
|
339
|
+
var allowedHosts = new[] { "example.com", "www.example.com" };
|
|
340
|
+
var host = context.Request.Host.Host;
|
|
341
|
+
|
|
342
|
+
if (!allowedHosts.Contains(host)) {
|
|
343
|
+
context.Response.StatusCode = 400;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!context.Request.IsHttps) {
|
|
348
|
+
var redirectUrl = $"https://{host}{context.Request.Path}";
|
|
349
|
+
context.Response.Redirect(redirectUrl, permanent: true);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await next();
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Rails 7.1:**
|
|
358
|
+
```ruby
|
|
359
|
+
# config/environments/production.rb
|
|
360
|
+
config.force_ssl = true
|
|
361
|
+
config.ssl_options = {
|
|
362
|
+
redirect: {
|
|
363
|
+
exclude: ->(request) {
|
|
364
|
+
!['example.com', 'www.example.com'].include?(request.host)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Fix Required
|
|
371
|
+
|
|
372
|
+
Add host validation:
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
376
|
+
var $that = this;
|
|
377
|
+
return http.createServer(function (req, res) {
|
|
378
|
+
try{
|
|
379
|
+
var host = req.headers['host'] || '';
|
|
380
|
+
|
|
381
|
+
// CRITICAL: Validate host header to prevent open redirect
|
|
382
|
+
if (allowedHosts.length > 0) {
|
|
383
|
+
const hostname = host.split(':')[0]; // Remove port
|
|
384
|
+
if (!allowedHosts.includes(hostname)) {
|
|
385
|
+
res.statusCode = 400;
|
|
386
|
+
res.end('Bad Request: Invalid host');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Redirect to HTTPS
|
|
392
|
+
var location = 'https://' + host + req.url;
|
|
393
|
+
res.statusCode = 301;
|
|
394
|
+
res.setHeader('Location', location);
|
|
395
|
+
res.end();
|
|
396
|
+
}catch(e){
|
|
397
|
+
res.statusCode = 500;
|
|
398
|
+
res.end();
|
|
399
|
+
}
|
|
400
|
+
}).listen(redirectPort, bindHost);
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Usage:**
|
|
405
|
+
```javascript
|
|
406
|
+
// In production, always specify allowed hosts
|
|
407
|
+
const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
|
|
408
|
+
'example.com',
|
|
409
|
+
'www.example.com',
|
|
410
|
+
'api.example.com'
|
|
411
|
+
]);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Issue #6: Static File Serving - Path Traversal Vulnerability
|
|
417
|
+
|
|
418
|
+
**Severity:** 🔴 **CRITICAL**
|
|
419
|
+
|
|
420
|
+
### Current State
|
|
421
|
+
```javascript
|
|
422
|
+
// MasterControl.js:482 - No path validation!
|
|
423
|
+
let pathname = `.${ctx.request.url}`;
|
|
424
|
+
|
|
425
|
+
fs.exists(pathname, function (exist) {
|
|
426
|
+
if (!exist) {
|
|
427
|
+
ctx.response.statusCode = 404;
|
|
428
|
+
ctx.response.end(`File ${pathname} not found!`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Reads file without validating path!
|
|
433
|
+
fs.readFile(pathname, function(err, data) {
|
|
434
|
+
// ...
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Vulnerability
|
|
440
|
+
An attacker can use `../` sequences to read arbitrary files:
|
|
441
|
+
|
|
442
|
+
**Attack Example:**
|
|
443
|
+
```bash
|
|
444
|
+
# Read /etc/passwd
|
|
445
|
+
curl http://example.com/../../../etc/passwd
|
|
446
|
+
|
|
447
|
+
# Read source code
|
|
448
|
+
curl http://example.com/../../../server.js
|
|
449
|
+
|
|
450
|
+
# Read environment files
|
|
451
|
+
curl http://example.com/../../../.env
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Comparison with Other Frameworks
|
|
455
|
+
|
|
456
|
+
**Express (express.static):**
|
|
457
|
+
```javascript
|
|
458
|
+
// Built-in path traversal protection
|
|
459
|
+
app.use(express.static('public', {
|
|
460
|
+
dotfiles: 'deny', // Block .env, .git, etc.
|
|
461
|
+
index: false,
|
|
462
|
+
maxAge: '1d',
|
|
463
|
+
redirect: false,
|
|
464
|
+
setHeaders: (res, path) => {
|
|
465
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
466
|
+
}
|
|
467
|
+
}));
|
|
468
|
+
|
|
469
|
+
// Internally uses path normalization:
|
|
470
|
+
const safePath = path.normalize(requestedPath);
|
|
471
|
+
if (!safePath.startsWith(rootPath)) {
|
|
472
|
+
return res.status(403).send('Forbidden');
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**ASP.NET Core 8:**
|
|
477
|
+
```csharp
|
|
478
|
+
// Built-in static files middleware with security
|
|
479
|
+
app.UseStaticFiles(new StaticFileOptions {
|
|
480
|
+
ServeUnknownFileTypes = false,
|
|
481
|
+
OnPrepareResponse = ctx => {
|
|
482
|
+
// Prevent path traversal
|
|
483
|
+
var path = ctx.File.PhysicalPath;
|
|
484
|
+
var root = Path.GetFullPath("wwwroot");
|
|
485
|
+
if (!path.StartsWith(root)) {
|
|
486
|
+
ctx.Context.Response.StatusCode = 403;
|
|
487
|
+
ctx.Context.Response.ContentLength = 0;
|
|
488
|
+
ctx.Context.Response.Body = Stream.Null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Rails 7.1:**
|
|
495
|
+
```ruby
|
|
496
|
+
# Built-in protection via ActionDispatch::FileHandler
|
|
497
|
+
config.public_file_server.enabled = true
|
|
498
|
+
# Automatically sanitizes paths and blocks ../ traversal
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Fix Required
|
|
502
|
+
|
|
503
|
+
Add path validation:
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
// _registerCoreMiddleware() - lines 479-507
|
|
507
|
+
$that.pipeline.use(async (ctx, next) => {
|
|
508
|
+
if (ctx.isStatic) {
|
|
509
|
+
let pathname = `.${ctx.request.url}`;
|
|
510
|
+
|
|
511
|
+
// CRITICAL: Prevent path traversal attacks
|
|
512
|
+
const path = require('path');
|
|
513
|
+
const publicRoot = path.resolve('.'); // Or master.root + '/public'
|
|
514
|
+
const requestedPath = path.resolve(pathname);
|
|
515
|
+
|
|
516
|
+
// Ensure requested path is within public root
|
|
517
|
+
if (!requestedPath.startsWith(publicRoot)) {
|
|
518
|
+
ctx.response.statusCode = 403;
|
|
519
|
+
ctx.response.end('Forbidden');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Block dotfiles (.env, .git, etc.)
|
|
524
|
+
const filename = path.basename(requestedPath);
|
|
525
|
+
if (filename.startsWith('.')) {
|
|
526
|
+
ctx.response.statusCode = 403;
|
|
527
|
+
ctx.response.end('Forbidden');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fs.exists(requestedPath, function (exist) {
|
|
532
|
+
if (!exist) {
|
|
533
|
+
ctx.response.statusCode = 404;
|
|
534
|
+
ctx.response.end('Not Found');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (fs.statSync(requestedPath).isDirectory()) {
|
|
539
|
+
requestedPath += '/index.html';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
fs.readFile(requestedPath, function(err, data) {
|
|
543
|
+
if (err) {
|
|
544
|
+
ctx.response.statusCode = 500;
|
|
545
|
+
ctx.response.end('Internal Server Error');
|
|
546
|
+
} else {
|
|
547
|
+
const mimeType = $that.router.findMimeType(path.extname(requestedPath));
|
|
548
|
+
ctx.response.setHeader('Content-Type', mimeType || 'text/plain');
|
|
549
|
+
ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
550
|
+
ctx.response.end(data);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return; // Terminal
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
await next();
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Issue #7: Error Responses Leak Stack Traces
|
|
565
|
+
|
|
566
|
+
**Severity:** 🟡 **MEDIUM**
|
|
567
|
+
|
|
568
|
+
### Current State
|
|
569
|
+
```javascript
|
|
570
|
+
// MasterControl.js:559-577 - Global error handler
|
|
571
|
+
$that.pipeline.useError(async (error, ctx, next) => {
|
|
572
|
+
logger.error({
|
|
573
|
+
code: 'MC_ERR_PIPELINE',
|
|
574
|
+
message: 'Error in middleware pipeline',
|
|
575
|
+
error: error.message,
|
|
576
|
+
stack: error.stack, // Logged (good)
|
|
577
|
+
path: ctx.request.url,
|
|
578
|
+
method: ctx.type
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
if (!ctx.response.headersSent) {
|
|
582
|
+
ctx.response.statusCode = 500;
|
|
583
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
584
|
+
ctx.response.end(JSON.stringify({
|
|
585
|
+
error: 'Internal Server Error',
|
|
586
|
+
message: process.env.NODE_ENV === 'production'
|
|
587
|
+
? 'An error occurred'
|
|
588
|
+
: error.message // ⚠️ Leaks error details in dev
|
|
589
|
+
}));
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Issue
|
|
595
|
+
While better than many frameworks (checks NODE_ENV), it should use `master.environmentType` for consistency and never leak stack traces.
|
|
596
|
+
|
|
597
|
+
### Comparison with Other Frameworks
|
|
598
|
+
|
|
599
|
+
**Express:**
|
|
600
|
+
```javascript
|
|
601
|
+
app.use((err, req, res, next) => {
|
|
602
|
+
res.status(500);
|
|
603
|
+
if (process.env.NODE_ENV === 'production') {
|
|
604
|
+
res.json({ error: 'Internal Server Error' });
|
|
605
|
+
} else {
|
|
606
|
+
res.json({
|
|
607
|
+
error: err.message,
|
|
608
|
+
stack: err.stack // Dev only
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**ASP.NET Core 8:**
|
|
615
|
+
```csharp
|
|
616
|
+
if (env.IsDevelopment()) {
|
|
617
|
+
app.UseDeveloperExceptionPage(); // Stack traces in dev
|
|
618
|
+
} else {
|
|
619
|
+
app.UseExceptionHandler("/Error"); // Generic error page in prod
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Fix Required
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
$that.pipeline.useError(async (error, ctx, next) => {
|
|
627
|
+
logger.error({
|
|
628
|
+
code: 'MC_ERR_PIPELINE',
|
|
629
|
+
message: 'Error in middleware pipeline',
|
|
630
|
+
error: error.message,
|
|
631
|
+
stack: error.stack,
|
|
632
|
+
path: ctx.request.url,
|
|
633
|
+
method: ctx.type
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (!ctx.response.headersSent) {
|
|
637
|
+
const isDev = $that.environmentType === 'development';
|
|
638
|
+
|
|
639
|
+
ctx.response.statusCode = 500;
|
|
640
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
641
|
+
ctx.response.end(JSON.stringify({
|
|
642
|
+
error: 'Internal Server Error',
|
|
643
|
+
...(isDev && {
|
|
644
|
+
message: error.message,
|
|
645
|
+
stack: error.stack
|
|
646
|
+
})
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## Strengths: What MasterController Does Well
|
|
655
|
+
|
|
656
|
+
### ✅ 1. SNI (Server Name Indication) Support
|
|
657
|
+
|
|
658
|
+
**Excellent implementation!** Most Node.js frameworks require manual SNI setup.
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
// MasterControl.js:377-398 - SNI with multiple domains
|
|
662
|
+
var sniMap = {};
|
|
663
|
+
if(tlsCfg.sni && typeof tlsCfg.sni === 'object'){
|
|
664
|
+
for (var domain in tlsCfg.sni){
|
|
665
|
+
var domCreds = this._buildSecureContextFromPaths(tlsCfg.sni[domain]);
|
|
666
|
+
if(domCreds){
|
|
667
|
+
sniMap[domain] = tls.createSecureContext(domCreds);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
options.SNICallback = function(servername, cb){
|
|
673
|
+
var ctx = sniMap[servername];
|
|
674
|
+
if(!ctx && defaultContext){ ctx = defaultContext; }
|
|
675
|
+
if(cb){ return cb(null, ctx); }
|
|
676
|
+
return ctx;
|
|
677
|
+
};
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Comparison:**
|
|
681
|
+
- **Express:** Requires manual SNI setup
|
|
682
|
+
- **ASP.NET Core:** Built-in SNI support
|
|
683
|
+
- **Rails/Puma:** Requires manual configuration
|
|
684
|
+
- **MasterController:** ✅ Built-in, easy configuration
|
|
685
|
+
|
|
686
|
+
### ✅ 2. TLS Certificate Live Reload
|
|
687
|
+
|
|
688
|
+
**Outstanding feature!** Zero-downtime certificate renewal.
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
// MasterControl.js:454-469 - Watch files and reload
|
|
692
|
+
_watchTlsFilesAndReload(desc, onChange){
|
|
693
|
+
var paths = [];
|
|
694
|
+
if(desc.keyPath){ paths.push(desc.keyPath); }
|
|
695
|
+
if(desc.certPath){ paths.push(desc.certPath); }
|
|
696
|
+
paths.forEach(function(p){
|
|
697
|
+
fs.watchFile(p, { interval: 5000 }, function(){
|
|
698
|
+
onChange();
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**Comparison:**
|
|
705
|
+
- **Express:** Manual restart required
|
|
706
|
+
- **ASP.NET Core:** Requires manual restart or IIS binding
|
|
707
|
+
- **Rails:** Manual restart required
|
|
708
|
+
- **MasterController:** ✅ Automatic reload, zero downtime
|
|
709
|
+
|
|
710
|
+
### ✅ 3. HTTP to HTTPS Redirect Server
|
|
711
|
+
|
|
712
|
+
**Well-designed!** (But needs host validation - see Issue #5)
|
|
713
|
+
|
|
714
|
+
```javascript
|
|
715
|
+
// MasterControl.js:348-363
|
|
716
|
+
startHttpToHttpsRedirect(redirectPort, bindHost){
|
|
717
|
+
return http.createServer(function (req, res) {
|
|
718
|
+
var location = 'https://' + host + req.url;
|
|
719
|
+
res.statusCode = 301;
|
|
720
|
+
res.setHeader('Location', location);
|
|
721
|
+
res.end();
|
|
722
|
+
}).listen(redirectPort, bindHost);
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Comparison:**
|
|
727
|
+
- **Express:** Requires middleware
|
|
728
|
+
- **ASP.NET Core:** Built-in with `UseHttpsRedirection()`
|
|
729
|
+
- **Rails:** Requires `config.force_ssl`
|
|
730
|
+
- **MasterController:** ✅ Dedicated redirect server (good pattern)
|
|
731
|
+
|
|
732
|
+
### ✅ 4. Security Headers Middleware
|
|
733
|
+
|
|
734
|
+
**Comprehensive implementation!**
|
|
735
|
+
|
|
736
|
+
```javascript
|
|
737
|
+
// security/SecurityMiddleware.js:19-40
|
|
738
|
+
const SECURITY_HEADERS = {
|
|
739
|
+
'X-XSS-Protection': '1; mode=block',
|
|
740
|
+
'X-Frame-Options': 'SAMEORIGIN',
|
|
741
|
+
'X-Content-Type-Options': 'nosniff',
|
|
742
|
+
'X-DNS-Prefetch-Control': 'off',
|
|
743
|
+
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
|
|
744
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
745
|
+
'X-Powered-By': ''
|
|
746
|
+
};
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
**Comparison:**
|
|
750
|
+
- **Express:** Requires helmet package
|
|
751
|
+
- **ASP.NET Core:** Requires manual headers
|
|
752
|
+
- **Rails:** Requires secure_headers gem
|
|
753
|
+
- **MasterController:** ✅ Built-in, comprehensive
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Comparison Table: MasterController vs Other Frameworks
|
|
758
|
+
|
|
759
|
+
| Feature | MasterController | Express | ASP.NET Core 8 | Rails 7.1 | Django 5.0 |
|
|
760
|
+
|---------|------------------|---------|----------------|-----------|------------|
|
|
761
|
+
| **TLS Version** | TLS 1.2 (default) | Node.js default | TLS 1.2+ | TLS 1.2+ | TLS 1.2+ |
|
|
762
|
+
| **Cipher Suites** | None (uses Node.js) | Manual | Configurable | Manual | Manual |
|
|
763
|
+
| **SNI Support** | ✅ Built-in | Manual | ✅ Built-in | Manual | Manual |
|
|
764
|
+
| **Cert Live Reload** | ✅ Automatic | ❌ Manual restart | ❌ Manual | ❌ Manual | ❌ Manual |
|
|
765
|
+
| **HSTS** | ⚠️ Partial (broken) | Via helmet | ✅ Built-in | ✅ Built-in | ✅ Built-in |
|
|
766
|
+
| **HTTP Redirect** | ⚠️ Vulnerable | Via middleware | ✅ Built-in | ✅ Built-in | ✅ Built-in |
|
|
767
|
+
| **Security Headers** | ✅ Built-in | Via helmet | Manual | Via gem | Manual |
|
|
768
|
+
| **Path Traversal Protection** | ❌ Vulnerable | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected |
|
|
769
|
+
| **HTTPS by Default** | ❌ Manual | ❌ Manual | ✅ Dev mode | ❌ Manual | ❌ Manual |
|
|
770
|
+
| **CSP Support** | ❌ None | Via helmet | Via middleware | Via gem | Via middleware |
|
|
771
|
+
|
|
772
|
+
**Legend:**
|
|
773
|
+
- ✅ **Good:** Feature exists and works correctly
|
|
774
|
+
- ⚠️ **Partial:** Feature exists but has issues
|
|
775
|
+
- ❌ **Missing:** Feature not implemented or requires manual setup
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## Recommendations
|
|
780
|
+
|
|
781
|
+
### Priority 1: Critical Fixes (Must Fix Before Production)
|
|
782
|
+
|
|
783
|
+
1. **Add `enableHSTS()` method** - Documented API that doesn't exist
|
|
784
|
+
2. **Fix HSTS max-age** - Use configured value, not hardcoded
|
|
785
|
+
3. **Add host validation to HTTP redirect** - Prevent open redirect attacks
|
|
786
|
+
4. **Fix path traversal vulnerability** - Validate and normalize file paths
|
|
787
|
+
5. **Update TLS default to 1.3** - Align with 2026 security standards
|
|
788
|
+
6. **Add secure cipher suite defaults** - Use Mozilla Intermediate config
|
|
789
|
+
|
|
790
|
+
### Priority 2: High Priority (Recommended)
|
|
791
|
+
|
|
792
|
+
7. **Add Content Security Policy (CSP)** - Modern XSS protection
|
|
793
|
+
8. **Add rate limiting to redirector** - Prevent abuse
|
|
794
|
+
9. **Improve error handling** - Use `master.environmentType` consistently
|
|
795
|
+
10. **Add OCSP stapling** - Better certificate validation
|
|
796
|
+
|
|
797
|
+
### Priority 3: Nice to Have
|
|
798
|
+
|
|
799
|
+
11. **Add HTTP/2 push support** - Performance optimization
|
|
800
|
+
12. **Add certificate expiry warnings** - Proactive monitoring
|
|
801
|
+
13. **Add TLS session resumption** - Performance optimization
|
|
802
|
+
14. **Add Expect-CT header** - Certificate Transparency
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## Fixed Implementation Examples
|
|
807
|
+
|
|
808
|
+
### Complete Secure HTTPS Setup
|
|
809
|
+
|
|
810
|
+
```javascript
|
|
811
|
+
// MasterControl.js - Secure defaults
|
|
812
|
+
setupServer(type, credentials){
|
|
813
|
+
// ... (auto-load internal modules) ...
|
|
814
|
+
|
|
815
|
+
if(type === "https"){
|
|
816
|
+
$that.serverProtocol = "https";
|
|
817
|
+
|
|
818
|
+
// Initialize TLS from env if no credentials passed
|
|
819
|
+
if(!credentials){
|
|
820
|
+
$that._initializeTlsFromEnv();
|
|
821
|
+
credentials = $that._tlsOptions;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Apply SECURE defaults (2026 standards)
|
|
825
|
+
if(credentials){
|
|
826
|
+
// TLS 1.3 by default (fallback to 1.2 for compatibility)
|
|
827
|
+
if(!credentials.minVersion){
|
|
828
|
+
credentials.minVersion = 'TLSv1.3';
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Secure cipher suites (Mozilla Intermediate 2026)
|
|
832
|
+
if(!credentials.ciphers){
|
|
833
|
+
credentials.ciphers = [
|
|
834
|
+
// TLS 1.3 ciphers
|
|
835
|
+
'TLS_AES_256_GCM_SHA384',
|
|
836
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
837
|
+
'TLS_AES_128_GCM_SHA256',
|
|
838
|
+
// TLS 1.2 ciphers (backward compatibility)
|
|
839
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
840
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
841
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
842
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
843
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
844
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
845
|
+
].join(':');
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Server prefers cipher order
|
|
849
|
+
if(credentials.honorCipherOrder === undefined){
|
|
850
|
+
credentials.honorCipherOrder = true;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// HTTP/2 and HTTP/1.1 support
|
|
854
|
+
if(!credentials.ALPNProtocols){
|
|
855
|
+
credentials.ALPNProtocols = ['h2', 'http/1.1'];
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const server = https.createServer(credentials, async function(req, res) {
|
|
859
|
+
$that.serverRun(req, res);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
$that.server = server;
|
|
863
|
+
return server;
|
|
864
|
+
}else{
|
|
865
|
+
throw new Error('HTTPS requires TLS credentials (key and cert)');
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Add enableHSTS() method
|
|
871
|
+
enableHSTS(options = {}) {
|
|
872
|
+
this._hstsEnabled = true;
|
|
873
|
+
this._hstsMaxAge = options.maxAge || 31536000; // 1 year default
|
|
874
|
+
this._hstsIncludeSubDomains = options.includeSubDomains !== false;
|
|
875
|
+
this._hstsPreload = options.preload === true;
|
|
876
|
+
|
|
877
|
+
return this; // Chainable
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Fix HSTS middleware
|
|
881
|
+
_registerCoreMiddleware(){
|
|
882
|
+
// ... (other middleware) ...
|
|
883
|
+
|
|
884
|
+
// HSTS Header (if enabled for HTTPS)
|
|
885
|
+
$that.pipeline.use(async (ctx, next) => {
|
|
886
|
+
if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
|
|
887
|
+
let hstsValue = `max-age=${$that._hstsMaxAge}`;
|
|
888
|
+
if ($that._hstsIncludeSubDomains) hstsValue += '; includeSubDomains';
|
|
889
|
+
if ($that._hstsPreload) hstsValue += '; preload';
|
|
890
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
891
|
+
}
|
|
892
|
+
await next();
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Fix HTTP to HTTPS redirect with host validation
|
|
897
|
+
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
898
|
+
var $that = this;
|
|
899
|
+
|
|
900
|
+
return http.createServer(function (req, res) {
|
|
901
|
+
try{
|
|
902
|
+
var host = req.headers['host'] || '';
|
|
903
|
+
var hostname = host.split(':')[0]; // Remove port
|
|
904
|
+
|
|
905
|
+
// CRITICAL: Validate host header
|
|
906
|
+
if (allowedHosts.length > 0 && !allowedHosts.includes(hostname)) {
|
|
907
|
+
res.statusCode = 400;
|
|
908
|
+
res.end('Bad Request');
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Redirect to HTTPS
|
|
913
|
+
var location = 'https://' + host + req.url;
|
|
914
|
+
res.statusCode = 301;
|
|
915
|
+
res.setHeader('Location', location);
|
|
916
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
917
|
+
res.end();
|
|
918
|
+
}catch(e){
|
|
919
|
+
logger.error({
|
|
920
|
+
code: 'MC_ERR_REDIRECT',
|
|
921
|
+
message: 'HTTP to HTTPS redirect failed',
|
|
922
|
+
error: e.message
|
|
923
|
+
});
|
|
924
|
+
res.statusCode = 500;
|
|
925
|
+
res.end();
|
|
926
|
+
}
|
|
927
|
+
}).listen(redirectPort, bindHost);
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### Production-Ready Usage
|
|
932
|
+
|
|
933
|
+
```javascript
|
|
934
|
+
// server.js
|
|
935
|
+
const master = require('mastercontroller');
|
|
936
|
+
const fs = require('fs');
|
|
937
|
+
|
|
938
|
+
master.environmentType = 'production';
|
|
939
|
+
master.root = __dirname;
|
|
940
|
+
|
|
941
|
+
// Setup HTTPS with secure defaults
|
|
942
|
+
const server = master.setupServer('https', {
|
|
943
|
+
key: fs.readFileSync('/path/to/privkey.pem'),
|
|
944
|
+
cert: fs.readFileSync('/path/to/fullchain.pem')
|
|
945
|
+
// minVersion, ciphers, etc. automatically set to secure defaults
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
// Enable HSTS for production
|
|
949
|
+
master.enableHSTS({
|
|
950
|
+
maxAge: 31536000, // 1 year
|
|
951
|
+
includeSubDomains: true,
|
|
952
|
+
preload: true // Submit to HSTS preload list
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// Load config
|
|
956
|
+
require('./config/initializers/config');
|
|
957
|
+
|
|
958
|
+
// Start HTTPS server on 443
|
|
959
|
+
master.start(server);
|
|
960
|
+
master.serverSettings({ httpPort: 443 });
|
|
961
|
+
|
|
962
|
+
// Start HTTP to HTTPS redirect on port 80
|
|
963
|
+
const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
|
|
964
|
+
'example.com',
|
|
965
|
+
'www.example.com',
|
|
966
|
+
'api.example.com'
|
|
967
|
+
]);
|
|
968
|
+
|
|
969
|
+
console.log('✅ HTTPS server running on port 443');
|
|
970
|
+
console.log('✅ HTTP redirect server running on port 80');
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
## Testing Checklist
|
|
976
|
+
|
|
977
|
+
### TLS/HTTPS Tests
|
|
978
|
+
|
|
979
|
+
- [ ] TLS 1.3 handshake succeeds
|
|
980
|
+
- [ ] TLS 1.2 handshake succeeds (backward compatibility)
|
|
981
|
+
- [ ] TLS 1.1 handshake fails (blocked)
|
|
982
|
+
- [ ] TLS 1.0 handshake fails (blocked)
|
|
983
|
+
- [ ] Weak ciphers rejected
|
|
984
|
+
- [ ] Strong ciphers accepted
|
|
985
|
+
- [ ] SNI routing works for multiple domains
|
|
986
|
+
- [ ] Certificate reload works without downtime
|
|
987
|
+
- [ ] HSTS header present with correct max-age
|
|
988
|
+
- [ ] HSTS includeSubDomains works
|
|
989
|
+
- [ ] HTTP to HTTPS redirect works
|
|
990
|
+
- [ ] Invalid host header rejected (400 Bad Request)
|
|
991
|
+
- [ ] Path traversal attacks blocked
|
|
992
|
+
- [ ] Dotfiles blocked
|
|
993
|
+
- [ ] Security headers present
|
|
994
|
+
|
|
995
|
+
### Tools for Testing
|
|
996
|
+
|
|
997
|
+
**SSL Labs:**
|
|
998
|
+
```bash
|
|
999
|
+
# Test your HTTPS configuration
|
|
1000
|
+
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
**testssl.sh:**
|
|
1004
|
+
```bash
|
|
1005
|
+
# Comprehensive TLS testing
|
|
1006
|
+
./testssl.sh --full https://example.com
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
**nmap:**
|
|
1010
|
+
```bash
|
|
1011
|
+
# Check TLS versions and ciphers
|
|
1012
|
+
nmap --script ssl-enum-ciphers -p 443 example.com
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
**curl:**
|
|
1016
|
+
```bash
|
|
1017
|
+
# Test TLS 1.3
|
|
1018
|
+
curl -v --tlsv1.3 https://example.com
|
|
1019
|
+
|
|
1020
|
+
# Test HSTS
|
|
1021
|
+
curl -I https://example.com | grep Strict-Transport-Security
|
|
1022
|
+
|
|
1023
|
+
# Test redirect
|
|
1024
|
+
curl -I http://example.com
|
|
1025
|
+
|
|
1026
|
+
# Test path traversal
|
|
1027
|
+
curl http://example.com/../../../etc/passwd
|
|
1028
|
+
|
|
1029
|
+
# Test open redirect
|
|
1030
|
+
curl -H "Host: evil.com" http://example.com -I
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
---
|
|
1034
|
+
|
|
1035
|
+
## Conclusion
|
|
1036
|
+
|
|
1037
|
+
MasterController has **excellent TLS features** (SNI, certificate reload) that surpass many popular frameworks, but has **critical security vulnerabilities** that must be fixed:
|
|
1038
|
+
|
|
1039
|
+
**Must Fix:**
|
|
1040
|
+
1. Path traversal vulnerability (file serving)
|
|
1041
|
+
2. Open redirect vulnerability (HTTP redirect)
|
|
1042
|
+
3. Missing `enableHSTS()` method
|
|
1043
|
+
4. HSTS configuration ignored
|
|
1044
|
+
5. Weak TLS/cipher defaults for 2026
|
|
1045
|
+
|
|
1046
|
+
**After fixes, MasterController will have:**
|
|
1047
|
+
- ✅ Best-in-class SNI support
|
|
1048
|
+
- ✅ Zero-downtime certificate reload
|
|
1049
|
+
- ✅ Comprehensive security headers
|
|
1050
|
+
- ✅ Modern TLS 1.3 defaults
|
|
1051
|
+
- ✅ Secure cipher suites
|
|
1052
|
+
- ✅ Production-ready HTTPS setup
|
|
1053
|
+
|
|
1054
|
+
**Estimated Fix Time:** 4-6 hours for all critical issues
|
|
1055
|
+
|
|
1056
|
+
**Risk Level After Fixes:** Low (production-ready)
|