mastercontroller 1.3.1 → 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 +6 -1
- package/MasterAction.js +158 -27
- package/MasterActionFilters.js +213 -93
- package/MasterControl.js +289 -43
- package/MasterCors.js +1 -2
- package/MasterHtml.js +243 -145
- package/MasterPipeline.js +2 -3
- package/MasterRequest.js +203 -26
- package/MasterRouter.js +1 -2
- package/MasterSocket.js +7 -3
- package/MasterTemp.js +1 -2
- package/MasterTimeout.js +2 -4
- package/MasterTools.js +388 -0
- package/README.md +2288 -369
- package/TemplateOverwrite.js +1 -1
- 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/error/MasterError.js +1 -2
- package/error/MasterErrorRenderer.js +1 -2
- package/package.json +1 -1
- package/security/SecurityEnforcement.js +241 -0
- package/security/SessionSecurity.js +6 -5
- 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,375 @@
|
|
|
1
|
+
# Security Quick Start Guide
|
|
2
|
+
|
|
3
|
+
**Goal:** Enable automatic security enforcement in 5 minutes
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1: Update config/initializers/config.js
|
|
8
|
+
|
|
9
|
+
Add these lines at the top of your config file:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// config/initializers/config.js
|
|
13
|
+
var master = require('mastercontroller');
|
|
14
|
+
const SecurityEnforcement = require('mastercontroller/security/SecurityEnforcement');
|
|
15
|
+
|
|
16
|
+
// ===========================
|
|
17
|
+
// AUTOMATIC SECURITY ENFORCEMENT
|
|
18
|
+
// ===========================
|
|
19
|
+
|
|
20
|
+
const securityConfig = SecurityEnforcement.init({
|
|
21
|
+
csrf: true, // Auto-validate CSRF tokens
|
|
22
|
+
sanitizeInputs: true, // Auto-sanitize all user inputs
|
|
23
|
+
httpsOnly: true, // Require HTTPS in production
|
|
24
|
+
csrfExcludePaths: [ // Paths that don't need CSRF (webhooks, APIs)
|
|
25
|
+
'/api/webhook',
|
|
26
|
+
'/api/public'
|
|
27
|
+
]
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Register security middleware (IMPORTANT!)
|
|
31
|
+
master.pipeline.use(SecurityEnforcement.middleware(securityConfig));
|
|
32
|
+
|
|
33
|
+
// ... rest of your config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Step 2: Configure Hostname
|
|
39
|
+
|
|
40
|
+
Add hostname to your production environment config:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
// config/environments/env.production.json
|
|
44
|
+
{
|
|
45
|
+
"server": {
|
|
46
|
+
"hostname": "yourapp.com",
|
|
47
|
+
"httpsPort": 443,
|
|
48
|
+
"requestTimeout": 120000
|
|
49
|
+
},
|
|
50
|
+
"error": {
|
|
51
|
+
"showStackTrace": false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Step 3: Include CSRF Tokens in Forms
|
|
59
|
+
|
|
60
|
+
### HTML Forms
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<form method="POST" action="/users">
|
|
64
|
+
<!-- Add CSRF token -->
|
|
65
|
+
<input type="hidden" name="_csrf" value="<%= this.generateCSRFToken() %>">
|
|
66
|
+
|
|
67
|
+
<input type="text" name="username">
|
|
68
|
+
<input type="email" name="email">
|
|
69
|
+
<button type="submit">Create User</button>
|
|
70
|
+
</form>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### AJAX Requests
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// Get CSRF token from meta tag
|
|
77
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
|
78
|
+
|
|
79
|
+
// Include in request
|
|
80
|
+
fetch('/api/users', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'X-CSRF-Token': csrfToken
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
username: 'john',
|
|
88
|
+
email: 'john@example.com'
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Add CSRF Meta Tag to Layout
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<!-- app/views/layouts/master.html -->
|
|
97
|
+
<!DOCTYPE html>
|
|
98
|
+
<html>
|
|
99
|
+
<head>
|
|
100
|
+
<meta name="csrf-token" content="<%= this.generateCSRFToken() %>">
|
|
101
|
+
<!-- ... other meta tags -->
|
|
102
|
+
</head>
|
|
103
|
+
<body>
|
|
104
|
+
<%= yield %>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Step 4: Test Your Security
|
|
112
|
+
|
|
113
|
+
### Test CSRF Protection
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Should FAIL (no CSRF token)
|
|
117
|
+
curl -X POST http://localhost:3000/users \
|
|
118
|
+
-H "Content-Type: application/json" \
|
|
119
|
+
-d '{"username":"test"}'
|
|
120
|
+
|
|
121
|
+
# Should SUCCEED (with CSRF token)
|
|
122
|
+
curl -X POST http://localhost:3000/users \
|
|
123
|
+
-H "Content-Type: application/json" \
|
|
124
|
+
-H "X-CSRF-Token: YOUR_TOKEN_HERE" \
|
|
125
|
+
-d '{"username":"test"}'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Test Input Sanitization
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// Try to inject XSS
|
|
132
|
+
const form = {
|
|
133
|
+
comment: '<script>alert("XSS")</script>'
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// After security enforcement, this will be sanitized automatically:
|
|
137
|
+
// "<script>alert("XSS")</script>" becomes "<script>alert("XSS")</script>"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Test HTTPS Enforcement
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# In production, HTTP request should redirect to HTTPS
|
|
144
|
+
curl -I http://yourapp.com/admin
|
|
145
|
+
|
|
146
|
+
# Should return:
|
|
147
|
+
# HTTP/1.1 301 Moved Permanently
|
|
148
|
+
# Location: https://yourapp.com/admin
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Step 5: Remove Manual Security Checks (Optional)
|
|
154
|
+
|
|
155
|
+
Since security is now automatic, you can remove redundant checks:
|
|
156
|
+
|
|
157
|
+
### Before (Manual)
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
class UsersController {
|
|
161
|
+
create(obj) {
|
|
162
|
+
// Manual security checks (can remove now)
|
|
163
|
+
if (!this.validateCSRF()) {
|
|
164
|
+
return this.returnError(403, 'CSRF invalid');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!this.requireHTTPS()) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const username = this.sanitizeInput(obj.params.formData.username);
|
|
172
|
+
const email = this.sanitizeInput(obj.params.formData.email);
|
|
173
|
+
|
|
174
|
+
// ... create user
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### After (Automatic)
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
class UsersController {
|
|
183
|
+
create(obj) {
|
|
184
|
+
// All security checks done automatically!
|
|
185
|
+
// Just handle the business logic
|
|
186
|
+
const username = obj.params.formData.username; // Already sanitized
|
|
187
|
+
const email = obj.params.formData.email; // Already sanitized
|
|
188
|
+
|
|
189
|
+
const user = this.userContext.create({ username, email });
|
|
190
|
+
this.json({ user });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Common Issues
|
|
198
|
+
|
|
199
|
+
### Issue 1: CSRF Token Missing
|
|
200
|
+
|
|
201
|
+
**Error:** `403 Forbidden - CSRF token required`
|
|
202
|
+
|
|
203
|
+
**Solution:** Include CSRF token in form or header:
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<!-- Option 1: Form field -->
|
|
207
|
+
<input type="hidden" name="_csrf" value="<%= this.generateCSRFToken() %>">
|
|
208
|
+
|
|
209
|
+
<!-- Option 2: AJAX header -->
|
|
210
|
+
<script>
|
|
211
|
+
fetch(url, {
|
|
212
|
+
headers: { 'X-CSRF-Token': '<%= this.generateCSRFToken() %>' }
|
|
213
|
+
});
|
|
214
|
+
</script>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Issue 2: Webhook Failing CSRF Check
|
|
218
|
+
|
|
219
|
+
**Error:** External webhook blocked by CSRF
|
|
220
|
+
|
|
221
|
+
**Solution:** Exclude webhook path from CSRF:
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const securityConfig = SecurityEnforcement.init({
|
|
225
|
+
csrf: true,
|
|
226
|
+
csrfExcludePaths: [
|
|
227
|
+
'/api/webhook', // Your webhook path
|
|
228
|
+
'/api/stripe/webhook', // Stripe webhook
|
|
229
|
+
'/api/github/webhook' // GitHub webhook
|
|
230
|
+
]
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Issue 3: HTTPS Redirect Loop
|
|
235
|
+
|
|
236
|
+
**Error:** Infinite redirect between HTTP and HTTPS
|
|
237
|
+
|
|
238
|
+
**Solution:** Configure hostname correctly:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"server": {
|
|
243
|
+
"hostname": "yourapp.com", // NOT "localhost"
|
|
244
|
+
"httpsPort": 443
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Issue 4: Input Sanitization Breaking HTML
|
|
250
|
+
|
|
251
|
+
**Error:** Rich text editor content gets sanitized
|
|
252
|
+
|
|
253
|
+
**Solution:** Disable sanitization for specific paths:
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
// Coming in v1.3.3 - For now, manually handle rich text:
|
|
257
|
+
class PostsController {
|
|
258
|
+
create(obj) {
|
|
259
|
+
// For rich text, use different validation
|
|
260
|
+
const body = obj.params.formData.body;
|
|
261
|
+
|
|
262
|
+
// Validate allowed HTML tags only
|
|
263
|
+
const result = this.validate(body, {
|
|
264
|
+
type: 'html',
|
|
265
|
+
allowedTags: ['p', 'strong', 'em', 'a', 'ul', 'li']
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!result.valid) {
|
|
269
|
+
return this.returnError(400, 'Invalid HTML');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ... create post
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Configuration Options
|
|
280
|
+
|
|
281
|
+
### Full Configuration
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
const securityConfig = SecurityEnforcement.init({
|
|
285
|
+
// CSRF Protection
|
|
286
|
+
csrf: true, // Enable CSRF validation
|
|
287
|
+
csrfExcludePaths: [ // Paths that skip CSRF check
|
|
288
|
+
'/api/webhook',
|
|
289
|
+
'/api/public'
|
|
290
|
+
],
|
|
291
|
+
|
|
292
|
+
// Input Sanitization
|
|
293
|
+
sanitizeInputs: true, // Auto-sanitize all inputs
|
|
294
|
+
|
|
295
|
+
// HTTPS Enforcement
|
|
296
|
+
httpsOnly: true, // Redirect HTTP to HTTPS (production only)
|
|
297
|
+
|
|
298
|
+
// Future Features
|
|
299
|
+
autoEscape: true, // Auto-escape template output (v1.3.3)
|
|
300
|
+
|
|
301
|
+
// Security Headers (always enabled)
|
|
302
|
+
headers: {
|
|
303
|
+
xss: true, // X-XSS-Protection
|
|
304
|
+
frameOptions: true, // X-Frame-Options
|
|
305
|
+
contentType: true, // X-Content-Type-Options
|
|
306
|
+
referrer: true, // Referrer-Policy
|
|
307
|
+
csp: true, // Content-Security-Policy
|
|
308
|
+
permissions: true // Permissions-Policy
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Minimal Configuration (Development)
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
// For development/testing - relaxed security
|
|
317
|
+
const securityConfig = SecurityEnforcement.init({
|
|
318
|
+
csrf: false, // Disable CSRF in development
|
|
319
|
+
sanitizeInputs: true, // Keep sanitization
|
|
320
|
+
httpsOnly: false // Allow HTTP in development
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Security Checklist
|
|
327
|
+
|
|
328
|
+
Use this checklist to ensure your app is secure:
|
|
329
|
+
|
|
330
|
+
- [ ] Security enforcement enabled in `config/initializers/config.js`
|
|
331
|
+
- [ ] Hostname configured in `config/environments/env.production.json`
|
|
332
|
+
- [ ] CSRF tokens included in all forms
|
|
333
|
+
- [ ] CSRF meta tag in layout
|
|
334
|
+
- [ ] AJAX requests include X-CSRF-Token header
|
|
335
|
+
- [ ] Webhook paths excluded from CSRF
|
|
336
|
+
- [ ] HTTPS certificate installed
|
|
337
|
+
- [ ] Testing: CSRF validation works
|
|
338
|
+
- [ ] Testing: XSS blocked in forms
|
|
339
|
+
- [ ] Testing: Path traversal blocked
|
|
340
|
+
- [ ] Testing: HTTPS redirect works
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Next Steps
|
|
345
|
+
|
|
346
|
+
1. **Run Security Tests:**
|
|
347
|
+
```bash
|
|
348
|
+
npm test test/security/
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
2. **Test with SSL Labs:**
|
|
352
|
+
```
|
|
353
|
+
https://www.ssllabs.com/ssltest/analyze.html?d=yourapp.com
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
3. **Test with Security Headers:**
|
|
357
|
+
```
|
|
358
|
+
https://securityheaders.com/?q=yourapp.com
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
4. **Read Full Documentation:**
|
|
362
|
+
- `SECURITY-FIXES-v1.3.2.md` - All fixes explained
|
|
363
|
+
- `SECURITY-AUDIT-ACTION-SYSTEM.md` - Security audit details
|
|
364
|
+
- `README.md` - General documentation
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Support
|
|
369
|
+
|
|
370
|
+
**Issues:** https://github.com/alexanderrich/MasterController/issues
|
|
371
|
+
**Security:** security@mastercontroller.com
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
**You're Done!** Your app now has industry-standard security.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Professional Timeout and Error Handling
|
|
2
2
|
|
|
3
|
-
MasterController
|
|
3
|
+
MasterController includes production-ready timeout tracking and error page rendering inspired by Rails and Django.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ The timeout system provides per-request timeout tracking with configurable optio
|
|
|
25
25
|
- **Detailed logging** of timeouts
|
|
26
26
|
- **Custom timeout handlers**
|
|
27
27
|
|
|
28
|
-
###Configuration
|
|
28
|
+
### Configuration
|
|
29
29
|
|
|
30
30
|
**config/initializers/config.js:**
|
|
31
31
|
|
|
@@ -339,7 +339,7 @@ Accept: application/json
|
|
|
339
339
|
|
|
340
340
|
## Migration Guide
|
|
341
341
|
|
|
342
|
-
### From
|
|
342
|
+
### From Previous Versions
|
|
343
343
|
|
|
344
344
|
**OLD - Static HTML in public/ folder:**
|
|
345
345
|
|
|
@@ -396,10 +396,12 @@ Accept: application/json
|
|
|
396
396
|
# Create error templates directory
|
|
397
397
|
mkdir -p public/errors
|
|
398
398
|
|
|
399
|
-
#
|
|
400
|
-
|
|
399
|
+
# Move existing error pages
|
|
400
|
+
mv public/404.html public/errors/
|
|
401
|
+
mv public/500.html public/errors/
|
|
401
402
|
|
|
402
|
-
#
|
|
403
|
+
# Create missing templates
|
|
404
|
+
touch public/errors/{400,401,403,405,422,429,502,503,504}.html
|
|
403
405
|
```
|
|
404
406
|
|
|
405
407
|
### Step 3: Update config.js
|
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
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// version 1.0 - Automatic Security Enforcement
|
|
2
|
+
// This middleware automatically enforces security best practices
|
|
3
|
+
var master = require('../MasterControl');
|
|
4
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
5
|
+
const { validateCSRFToken } = require('./SecurityMiddleware');
|
|
6
|
+
const { sanitizeObject } = require('./MasterValidator');
|
|
7
|
+
const { sanitizeUserHTML } = require('./MasterSanitizer');
|
|
8
|
+
|
|
9
|
+
class SecurityEnforcement {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initialize automatic security enforcement
|
|
13
|
+
* Call this in config/initializers/config.js:
|
|
14
|
+
* master.security.enforce({ csrf: true, sanitizeInputs: true, httpsOnly: true });
|
|
15
|
+
*/
|
|
16
|
+
static init(options = {}) {
|
|
17
|
+
const config = {
|
|
18
|
+
// Auto-enforce CSRF on POST/PUT/DELETE
|
|
19
|
+
csrf: options.csrf !== false, // Default: true
|
|
20
|
+
|
|
21
|
+
// Auto-sanitize all request inputs
|
|
22
|
+
sanitizeInputs: options.sanitizeInputs !== false, // Default: true
|
|
23
|
+
|
|
24
|
+
// Require HTTPS in production
|
|
25
|
+
httpsOnly: options.httpsOnly !== false, // Default: true
|
|
26
|
+
|
|
27
|
+
// Auto-escape template output (future enhancement)
|
|
28
|
+
autoEscape: options.autoEscape !== false, // Default: true
|
|
29
|
+
|
|
30
|
+
// Excluded paths (no CSRF check)
|
|
31
|
+
csrfExcludePaths: options.csrfExcludePaths || ['/api/webhook'],
|
|
32
|
+
|
|
33
|
+
// Allowed origins for CORS
|
|
34
|
+
allowedOrigins: options.allowedOrigins || [],
|
|
35
|
+
|
|
36
|
+
...options
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
logger.info({
|
|
40
|
+
code: 'MC_SECURITY_ENFORCEMENT_INIT',
|
|
41
|
+
message: 'Security enforcement initialized',
|
|
42
|
+
config: {
|
|
43
|
+
csrf: config.csrf,
|
|
44
|
+
sanitizeInputs: config.sanitizeInputs,
|
|
45
|
+
httpsOnly: config.httpsOnly
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get enforcement middleware for pipeline
|
|
54
|
+
* Automatically validates CSRF, sanitizes inputs, enforces HTTPS
|
|
55
|
+
*/
|
|
56
|
+
static middleware(config = {}) {
|
|
57
|
+
return async (ctx, next) => {
|
|
58
|
+
// 1. HTTPS Enforcement (Production only)
|
|
59
|
+
if (config.httpsOnly && master.environmentType === 'production') {
|
|
60
|
+
if (!SecurityEnforcement._isSecure(ctx.request)) {
|
|
61
|
+
logger.warn({
|
|
62
|
+
code: 'MC_SECURITY_HTTPS_REQUIRED',
|
|
63
|
+
message: 'HTTPS required in production',
|
|
64
|
+
path: ctx.pathName,
|
|
65
|
+
ip: ctx.request.connection.remoteAddress
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const configuredHost = master.env?.server?.hostname;
|
|
69
|
+
if (configuredHost && configuredHost !== 'localhost') {
|
|
70
|
+
const httpsPort = master.env?.server?.httpsPort || 443;
|
|
71
|
+
const port = httpsPort === 443 ? '' : `:${httpsPort}`;
|
|
72
|
+
const httpsUrl = `https://${configuredHost}${port}${ctx.request.url}`;
|
|
73
|
+
|
|
74
|
+
ctx.response.statusCode = 301;
|
|
75
|
+
ctx.response.setHeader('Location', httpsUrl);
|
|
76
|
+
ctx.response.end();
|
|
77
|
+
return; // Don't call next()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. CSRF Protection (POST, PUT, DELETE, PATCH)
|
|
83
|
+
if (config.csrf && ['post', 'put', 'delete', 'patch'].includes(ctx.type)) {
|
|
84
|
+
// Check if path is excluded
|
|
85
|
+
const isExcluded = config.csrfExcludePaths.some(excludePath => {
|
|
86
|
+
return ctx.pathName.startsWith(excludePath.replace(/^\//, ''));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!isExcluded) {
|
|
90
|
+
const token = ctx.request.headers['x-csrf-token'] ||
|
|
91
|
+
ctx.params?.formData?._csrf ||
|
|
92
|
+
ctx.params?.query?._csrf;
|
|
93
|
+
|
|
94
|
+
if (!token) {
|
|
95
|
+
logger.warn({
|
|
96
|
+
code: 'MC_SECURITY_CSRF_MISSING',
|
|
97
|
+
message: 'CSRF token missing',
|
|
98
|
+
path: ctx.pathName,
|
|
99
|
+
method: ctx.type,
|
|
100
|
+
ip: ctx.request.connection.remoteAddress
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
ctx.response.statusCode = 403;
|
|
104
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
105
|
+
ctx.response.end(JSON.stringify({
|
|
106
|
+
error: 'CSRF token required',
|
|
107
|
+
message: 'Include X-CSRF-Token header or _csrf field in request'
|
|
108
|
+
}));
|
|
109
|
+
return; // Don't call next()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const validation = validateCSRFToken(token);
|
|
113
|
+
if (!validation.valid) {
|
|
114
|
+
logger.warn({
|
|
115
|
+
code: 'MC_SECURITY_CSRF_INVALID',
|
|
116
|
+
message: 'CSRF token validation failed',
|
|
117
|
+
path: ctx.pathName,
|
|
118
|
+
reason: validation.reason,
|
|
119
|
+
ip: ctx.request.connection.remoteAddress
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
ctx.response.statusCode = 403;
|
|
123
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
124
|
+
ctx.response.end(JSON.stringify({
|
|
125
|
+
error: 'Invalid CSRF token',
|
|
126
|
+
message: validation.reason
|
|
127
|
+
}));
|
|
128
|
+
return; // Don't call next()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 3. Input Sanitization (All methods)
|
|
134
|
+
if (config.sanitizeInputs && ctx.params) {
|
|
135
|
+
try {
|
|
136
|
+
// Sanitize all input objects
|
|
137
|
+
if (ctx.params.formData) {
|
|
138
|
+
ctx.params.formData = SecurityEnforcement._sanitizeInputs(ctx.params.formData);
|
|
139
|
+
}
|
|
140
|
+
if (ctx.params.query) {
|
|
141
|
+
ctx.params.query = SecurityEnforcement._sanitizeInputs(ctx.params.query);
|
|
142
|
+
}
|
|
143
|
+
if (ctx.params.body) {
|
|
144
|
+
ctx.params.body = SecurityEnforcement._sanitizeInputs(ctx.params.body);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
logger.debug({
|
|
148
|
+
code: 'MC_SECURITY_SANITIZED',
|
|
149
|
+
message: 'Inputs sanitized',
|
|
150
|
+
path: ctx.pathName
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error({
|
|
154
|
+
code: 'MC_SECURITY_SANITIZE_ERROR',
|
|
155
|
+
message: 'Failed to sanitize inputs',
|
|
156
|
+
error: error.message,
|
|
157
|
+
path: ctx.pathName
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 4. Security Headers
|
|
163
|
+
SecurityEnforcement._applySecurityHeaders(ctx.response);
|
|
164
|
+
|
|
165
|
+
// Continue to next middleware
|
|
166
|
+
await next();
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if request is secure (HTTPS)
|
|
172
|
+
*/
|
|
173
|
+
static _isSecure(req) {
|
|
174
|
+
return req.connection.encrypted ||
|
|
175
|
+
req.headers['x-forwarded-proto'] === 'https';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Sanitize user inputs recursively
|
|
180
|
+
*/
|
|
181
|
+
static _sanitizeInputs(obj) {
|
|
182
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
183
|
+
return obj;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle arrays
|
|
187
|
+
if (Array.isArray(obj)) {
|
|
188
|
+
return obj.map(item => SecurityEnforcement._sanitizeInputs(item));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle objects
|
|
192
|
+
const sanitized = {};
|
|
193
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
194
|
+
if (typeof value === 'string') {
|
|
195
|
+
// Sanitize HTML in string values
|
|
196
|
+
sanitized[key] = sanitizeUserHTML(value);
|
|
197
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
198
|
+
// Recursively sanitize nested objects
|
|
199
|
+
sanitized[key] = SecurityEnforcement._sanitizeInputs(value);
|
|
200
|
+
} else {
|
|
201
|
+
// Keep other types as-is
|
|
202
|
+
sanitized[key] = value;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return sanitized;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Apply security headers to response
|
|
211
|
+
*/
|
|
212
|
+
static _applySecurityHeaders(response) {
|
|
213
|
+
if (response.headersSent || response._headerSent) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// XSS Protection
|
|
218
|
+
response.setHeader('X-XSS-Protection', '1; mode=block');
|
|
219
|
+
|
|
220
|
+
// Clickjacking Protection
|
|
221
|
+
response.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
222
|
+
|
|
223
|
+
// MIME Sniffing Protection
|
|
224
|
+
response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
225
|
+
|
|
226
|
+
// Referrer Policy
|
|
227
|
+
response.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
228
|
+
|
|
229
|
+
// Content Security Policy (basic)
|
|
230
|
+
response.setHeader('Content-Security-Policy', "default-src 'self'");
|
|
231
|
+
|
|
232
|
+
// Feature Policy / Permissions Policy
|
|
233
|
+
response.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Export for use in config
|
|
238
|
+
module.exports = SecurityEnforcement;
|
|
239
|
+
|
|
240
|
+
// Also extend master object
|
|
241
|
+
master.extend('securityEnforcement', SecurityEnforcement);
|