mastercontroller 1.2.12 → 1.2.14

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,319 @@
1
+ // version 1.0.0
2
+ // MasterController Content Security Policy (CSP) Configuration
3
+
4
+ /**
5
+ * Content Security Policy (CSP) configuration
6
+ * Helps prevent XSS, clickjacking, and other code injection attacks
7
+ */
8
+
9
+ const crypto = require('crypto');
10
+
11
+ /**
12
+ * CSP Presets for different environments
13
+ */
14
+
15
+ // Development CSP - more relaxed for hot reload, dev tools
16
+ const DEVELOPMENT_CSP = {
17
+ 'default-src': ["'self'"],
18
+ 'script-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'", "http://localhost:*", "ws://localhost:*"],
19
+ 'style-src': ["'self'", "'unsafe-inline'"],
20
+ 'img-src': ["'self'", 'data:', 'https:'],
21
+ 'font-src': ["'self'", 'data:'],
22
+ 'connect-src': ["'self'", 'http://localhost:*', 'ws://localhost:*'],
23
+ 'media-src': ["'self'"],
24
+ 'object-src': ["'none'"],
25
+ 'frame-src': ["'self'"],
26
+ 'base-uri': ["'self'"],
27
+ 'form-action': ["'self'"],
28
+ 'frame-ancestors': ["'self'"]
29
+ };
30
+
31
+ // Production CSP - strict security
32
+ const PRODUCTION_CSP = {
33
+ 'default-src': ["'self'"],
34
+ 'script-src': ["'self'"],
35
+ 'style-src': ["'self'"],
36
+ 'img-src': ["'self'", 'data:', 'https:'],
37
+ 'font-src': ["'self'"],
38
+ 'connect-src': ["'self'"],
39
+ 'media-src': ["'self'"],
40
+ 'object-src': ["'none'"],
41
+ 'frame-src': ["'none'"],
42
+ 'base-uri': ["'self'"],
43
+ 'form-action': ["'self'"],
44
+ 'frame-ancestors': ["'none'"],
45
+ 'upgrade-insecure-requests': []
46
+ };
47
+
48
+ // Production with CDN support
49
+ const PRODUCTION_CDN_CSP = {
50
+ 'default-src': ["'self'"],
51
+ 'script-src': ["'self'", 'https://cdn.jsdelivr.net', 'https://unpkg.com'],
52
+ 'style-src': ["'self'", 'https://cdn.jsdelivr.net', 'https://unpkg.com'],
53
+ 'img-src': ["'self'", 'data:', 'https:'],
54
+ 'font-src': ["'self'", 'https://cdn.jsdelivr.net', 'https://fonts.gstatic.com'],
55
+ 'connect-src': ["'self'", 'https://*.sentry.io'],
56
+ 'media-src': ["'self'"],
57
+ 'object-src': ["'none'"],
58
+ 'frame-src': ["'none'"],
59
+ 'base-uri': ["'self'"],
60
+ 'form-action': ["'self'"],
61
+ 'frame-ancestors': ["'none'"],
62
+ 'upgrade-insecure-requests': []
63
+ };
64
+
65
+ class CSPConfig {
66
+ constructor(options = {}) {
67
+ this.enabled = options.enabled !== false;
68
+ this.reportOnly = options.reportOnly || false;
69
+ this.reportUri = options.reportUri || null;
70
+ this.useNonce = options.useNonce || false;
71
+ this.useHash = options.useHash || false;
72
+
73
+ // Start with preset based on environment
74
+ const env = process.env.NODE_ENV || 'development';
75
+ let preset;
76
+
77
+ if (options.preset === 'production-cdn') {
78
+ preset = PRODUCTION_CDN_CSP;
79
+ } else if (env === 'production') {
80
+ preset = PRODUCTION_CSP;
81
+ } else {
82
+ preset = DEVELOPMENT_CSP;
83
+ }
84
+
85
+ // Merge custom directives with preset
86
+ this.directives = { ...preset, ...options.directives };
87
+
88
+ // Nonce store (cleared per request)
89
+ this.currentNonce = null;
90
+ }
91
+
92
+ /**
93
+ * Generate CSP header middleware
94
+ */
95
+ middleware() {
96
+ return (req, res, next) => {
97
+ if (!this.enabled) {
98
+ return next();
99
+ }
100
+
101
+ // Generate nonce for this request if needed
102
+ if (this.useNonce) {
103
+ this.currentNonce = this._generateNonce();
104
+ req.cspNonce = this.currentNonce;
105
+ }
106
+
107
+ // Build CSP header
108
+ const headerValue = this.buildHeader(req);
109
+
110
+ // Set header
111
+ const headerName = this.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy';
112
+ res.setHeader(headerName, headerValue);
113
+
114
+ next();
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Build CSP header value
120
+ */
121
+ buildHeader(req = null) {
122
+ const directives = [];
123
+
124
+ for (const [directive, values] of Object.entries(this.directives)) {
125
+ if (!values || values.length === 0) {
126
+ // Directive with no value (e.g., upgrade-insecure-requests)
127
+ directives.push(directive);
128
+ continue;
129
+ }
130
+
131
+ let sources = [...values];
132
+
133
+ // Add nonce to script-src and style-src if enabled
134
+ if (this.useNonce && this.currentNonce && (directive === 'script-src' || directive === 'style-src')) {
135
+ sources.push(`'nonce-${this.currentNonce}'`);
136
+ }
137
+
138
+ directives.push(`${directive} ${sources.join(' ')}`);
139
+ }
140
+
141
+ // Add report-uri if configured
142
+ if (this.reportUri) {
143
+ directives.push(`report-uri ${this.reportUri}`);
144
+ }
145
+
146
+ return directives.join('; ');
147
+ }
148
+
149
+ /**
150
+ * Generate nonce for inline scripts/styles
151
+ */
152
+ _generateNonce() {
153
+ return crypto.randomBytes(16).toString('base64');
154
+ }
155
+
156
+ /**
157
+ * Get current nonce (for use in templates)
158
+ */
159
+ getNonce() {
160
+ return this.currentNonce;
161
+ }
162
+
163
+ /**
164
+ * Add source to directive
165
+ */
166
+ addSource(directive, source) {
167
+ if (!this.directives[directive]) {
168
+ this.directives[directive] = [];
169
+ }
170
+
171
+ if (!this.directives[directive].includes(source)) {
172
+ this.directives[directive].push(source);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Remove source from directive
178
+ */
179
+ removeSource(directive, source) {
180
+ if (!this.directives[directive]) {
181
+ return;
182
+ }
183
+
184
+ const index = this.directives[directive].indexOf(source);
185
+ if (index > -1) {
186
+ this.directives[directive].splice(index, 1);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Set entire directive
192
+ */
193
+ setDirective(directive, sources) {
194
+ this.directives[directive] = Array.isArray(sources) ? sources : [sources];
195
+ }
196
+
197
+ /**
198
+ * Remove directive
199
+ */
200
+ removeDirective(directive) {
201
+ delete this.directives[directive];
202
+ }
203
+
204
+ /**
205
+ * Generate hash for inline script/style
206
+ * Use this to allow specific inline scripts without 'unsafe-inline'
207
+ */
208
+ generateHash(content, algorithm = 'sha256') {
209
+ const hash = crypto.createHash(algorithm).update(content).digest('base64');
210
+ return `'${algorithm}-${hash}'`;
211
+ }
212
+
213
+ /**
214
+ * Helper to create nonce attribute for templates
215
+ */
216
+ nonceAttr() {
217
+ return this.currentNonce ? ` nonce="${this.currentNonce}"` : '';
218
+ }
219
+
220
+ /**
221
+ * Enable/disable specific features
222
+ */
223
+ allowInlineScripts() {
224
+ this.addSource('script-src', "'unsafe-inline'");
225
+ }
226
+
227
+ allowInlineStyles() {
228
+ this.addSource('style-src', "'unsafe-inline'");
229
+ }
230
+
231
+ allowEval() {
232
+ this.addSource('script-src', "'unsafe-eval'");
233
+ }
234
+
235
+ allowFraming(sources = ["'self'"]) {
236
+ this.setDirective('frame-ancestors', sources);
237
+ }
238
+
239
+ allowForms(sources = ["'self'"]) {
240
+ this.setDirective('form-action', sources);
241
+ }
242
+
243
+ /**
244
+ * Add monitoring/analytics services
245
+ */
246
+ allowGoogleAnalytics() {
247
+ this.addSource('script-src', 'https://www.google-analytics.com');
248
+ this.addSource('connect-src', 'https://www.google-analytics.com');
249
+ this.addSource('img-src', 'https://www.google-analytics.com');
250
+ }
251
+
252
+ allowSentry() {
253
+ this.addSource('script-src', 'https://browser.sentry-cdn.com');
254
+ this.addSource('connect-src', 'https://*.sentry.io');
255
+ }
256
+
257
+ allowStripe() {
258
+ this.addSource('script-src', 'https://js.stripe.com');
259
+ this.addSource('frame-src', 'https://js.stripe.com');
260
+ this.addSource('connect-src', 'https://api.stripe.com');
261
+ }
262
+
263
+ /**
264
+ * Get CSP configuration for debugging
265
+ */
266
+ getConfig() {
267
+ return {
268
+ enabled: this.enabled,
269
+ reportOnly: this.reportOnly,
270
+ reportUri: this.reportUri,
271
+ useNonce: this.useNonce,
272
+ directives: this.directives
273
+ };
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Create CSP with common configurations
279
+ */
280
+
281
+ function createDevelopmentCSP() {
282
+ return new CSPConfig({
283
+ preset: 'development',
284
+ reportOnly: true
285
+ });
286
+ }
287
+
288
+ function createProductionCSP(options = {}) {
289
+ return new CSPConfig({
290
+ preset: 'production',
291
+ reportOnly: false,
292
+ useNonce: true,
293
+ ...options
294
+ });
295
+ }
296
+
297
+ function createProductionCDNCSP(options = {}) {
298
+ return new CSPConfig({
299
+ preset: 'production-cdn',
300
+ reportOnly: false,
301
+ useNonce: true,
302
+ ...options
303
+ });
304
+ }
305
+
306
+ // Create singleton instance based on environment
307
+ const env = process.env.NODE_ENV || 'development';
308
+ const csp = env === 'production' ? createProductionCSP() : createDevelopmentCSP();
309
+
310
+ module.exports = {
311
+ CSPConfig,
312
+ csp,
313
+ createDevelopmentCSP,
314
+ createProductionCSP,
315
+ createProductionCDNCSP,
316
+ DEVELOPMENT_CSP,
317
+ PRODUCTION_CSP,
318
+ PRODUCTION_CDN_CSP
319
+ };