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.
@@ -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 "&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;"
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 v2.0 includes production-ready timeout tracking and error page rendering inspired by Rails and Django.
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 Old System (v1.x)
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
- # Copy provided templates
400
- cp node_modules/mastercontroller/templates/errors/*.html public/errors/
399
+ # Move existing error pages
400
+ mv public/404.html public/errors/
401
+ mv public/500.html public/errors/
401
402
 
402
- # Or use your custom templates
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/package.json CHANGED
@@ -18,5 +18,5 @@
18
18
  "scripts": {
19
19
  "test": "echo \"Error: no test specified\" && exit 1"
20
20
  },
21
- "version": "1.3.1"
21
+ "version": "1.3.2"
22
22
  }
@@ -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);
@@ -501,11 +501,12 @@ class MasterSessionSecurity {
501
501
  }
502
502
  }
503
503
 
504
- // Auto-register with MasterController
505
- master.extend("session", MasterSessionSecurity);
504
+ // Note: Auto-registration with MasterController happens in init() to avoid circular dependency
505
+ // This is called when master.session.init() is invoked in config.js
506
506
 
507
507
  module.exports = {
508
508
  SessionSecurity,
509
+ MasterSessionSecurity,
509
510
  session,
510
511
  createSessionMiddleware,
511
512
  destroySession,