@dynamicu/chromedebug-mcp 2.2.0

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.
Files changed (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,371 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import bcrypt from 'bcrypt';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ // Configuration
12
+ const JWT_SECRET = process.env.JWT_SECRET || 'chrome-pilot-secret-key-change-in-production';
13
+ const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
14
+ const API_KEYS_FILE = path.join(__dirname, '../../config/api-keys.json');
15
+
16
+ // Ensure config directory exists
17
+ const configDir = path.dirname(API_KEYS_FILE);
18
+ if (!fs.existsSync(configDir)) {
19
+ fs.mkdirSync(configDir, { recursive: true });
20
+ }
21
+
22
+ // Load or initialize API keys
23
+ let apiKeys = [];
24
+ try {
25
+ if (fs.existsSync(API_KEYS_FILE)) {
26
+ apiKeys = JSON.parse(fs.readFileSync(API_KEYS_FILE, 'utf8'));
27
+ }
28
+ } catch (error) {
29
+ console.error('Error loading API keys:', error);
30
+ apiKeys = [];
31
+ }
32
+
33
+ // Save API keys to file
34
+ function saveApiKeys() {
35
+ try {
36
+ fs.writeFileSync(API_KEYS_FILE, JSON.stringify(apiKeys, null, 2));
37
+ } catch (error) {
38
+ console.error('Error saving API keys:', error);
39
+ }
40
+ }
41
+
42
+ // Roles and permissions
43
+ const ROLES = {
44
+ ADMIN: 'admin',
45
+ USER: 'user',
46
+ READONLY: 'readonly'
47
+ };
48
+
49
+ // MCP Service roles for internal service-to-service communication
50
+ const MCP_SERVICE_ROLES = {
51
+ MCP_CLIENT: 'mcp_client'
52
+ };
53
+
54
+ const PERMISSIONS = {
55
+ CHROME_CONTROL: 'chrome:control',
56
+ WORKFLOW_READ: 'workflow:read',
57
+ WORKFLOW_WRITE: 'workflow:write',
58
+ WORKFLOW_DELETE: 'workflow:delete',
59
+ FRAME_READ: 'frame:read',
60
+ FRAME_WRITE: 'frame:write',
61
+ ADMIN_MANAGE: 'admin:manage'
62
+ };
63
+
64
+ const ROLE_PERMISSIONS = {
65
+ [ROLES.ADMIN]: Object.values(PERMISSIONS),
66
+ [ROLES.USER]: [
67
+ PERMISSIONS.CHROME_CONTROL,
68
+ PERMISSIONS.WORKFLOW_READ,
69
+ PERMISSIONS.WORKFLOW_WRITE,
70
+ PERMISSIONS.WORKFLOW_DELETE,
71
+ PERMISSIONS.FRAME_READ,
72
+ PERMISSIONS.FRAME_WRITE
73
+ ],
74
+ [ROLES.READONLY]: [
75
+ PERMISSIONS.WORKFLOW_READ,
76
+ PERMISSIONS.FRAME_READ
77
+ ],
78
+ [MCP_SERVICE_ROLES.MCP_CLIENT]: [
79
+ PERMISSIONS.WORKFLOW_READ,
80
+ PERMISSIONS.FRAME_READ
81
+ ]
82
+ };
83
+
84
+ // Generate secure API key
85
+ export function generateApiKey(name, role = ROLES.USER) {
86
+ const apiKey = `cp_${uuidv4().replace(/-/g, '')}`;
87
+ const hashedKey = bcrypt.hashSync(apiKey, 12);
88
+
89
+ const keyData = {
90
+ id: uuidv4(),
91
+ name,
92
+ role,
93
+ hashedKey,
94
+ createdAt: new Date().toISOString(),
95
+ lastUsed: null,
96
+ active: true
97
+ };
98
+
99
+ apiKeys.push(keyData);
100
+ saveApiKeys();
101
+
102
+ return { apiKey, keyData: { ...keyData, hashedKey: undefined } };
103
+ }
104
+
105
+ // Validate API key
106
+ function validateApiKey(apiKey) {
107
+ if (!apiKey) return null;
108
+
109
+ for (const keyData of apiKeys) {
110
+ if (keyData.active && bcrypt.compareSync(apiKey, keyData.hashedKey)) {
111
+ // Update last used timestamp
112
+ keyData.lastUsed = new Date().toISOString();
113
+ saveApiKeys();
114
+ return keyData;
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+
120
+ // Generate JWT token
121
+ export function generateJWT(payload) {
122
+ return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
123
+ }
124
+
125
+ // Verify JWT token
126
+ function verifyJWT(token) {
127
+ try {
128
+ return jwt.verify(token, JWT_SECRET);
129
+ } catch (error) {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ // Authentication middleware
135
+ export function authenticate(req, res, next) {
136
+ // Debug logging for authentication flow
137
+ console.log(`[AUTH-DEBUG] ${new Date().toISOString()} - ${req.method} ${req.originalUrl}`);
138
+ console.log(`[AUTH-DEBUG] Client IP: ${req.ip}`);
139
+ console.log(`[AUTH-DEBUG] X-Forwarded-For: ${req.headers['x-forwarded-for'] || 'none'}`);
140
+ console.log(`[AUTH-DEBUG] User-Agent: ${req.headers['user-agent'] || 'none'}`);
141
+ console.log(`[AUTH-DEBUG] Authorization header present: ${!!req.headers.authorization}`);
142
+ console.log(`[AUTH-DEBUG] X-API-Key header present: ${!!req.headers['x-api-key']}`);
143
+ console.log(`[AUTH-DEBUG] X-Service-Token header present: ${!!req.headers['x-service-token']}`);
144
+
145
+ // Allow localhost requests from Chrome extension
146
+ const isLocalhost = req.ip === '::1' || req.ip === '127.0.0.1' || req.ip === '::ffff:127.0.0.1';
147
+ console.log(`[AUTH-DEBUG] Is localhost: ${isLocalhost}`);
148
+
149
+ if (isLocalhost) {
150
+ // Set a synthetic user for localhost requests with explicit permissions
151
+ req.user = {
152
+ id: 'localhost',
153
+ name: 'Chrome Extension (Localhost)',
154
+ role: 'USER',
155
+ authMethod: 'localhost-bypass',
156
+ permissions: [
157
+ PERMISSIONS.CHROME_CONTROL,
158
+ PERMISSIONS.WORKFLOW_READ,
159
+ PERMISSIONS.WORKFLOW_WRITE,
160
+ PERMISSIONS.WORKFLOW_DELETE,
161
+ PERMISSIONS.FRAME_READ,
162
+ PERMISSIONS.FRAME_WRITE
163
+ ]
164
+ };
165
+ console.log(`[AUTH-DEBUG] ✅ Localhost bypass granted for user:`, req.user);
166
+ return next();
167
+ }
168
+
169
+ const authHeader = req.headers.authorization;
170
+ const apiKey = req.headers['x-api-key'];
171
+ const serviceToken = req.headers['x-service-token'];
172
+
173
+ let user = null;
174
+
175
+ // Check Bearer token (JWT)
176
+ if (authHeader && authHeader.startsWith('Bearer ')) {
177
+ const token = authHeader.substring(7);
178
+ const decoded = verifyJWT(token);
179
+ if (decoded) {
180
+ user = decoded;
181
+ }
182
+ }
183
+
184
+ // Check API key
185
+ if (!user && apiKey) {
186
+ const keyData = validateApiKey(apiKey);
187
+ if (keyData) {
188
+ user = {
189
+ id: keyData.id,
190
+ name: keyData.name,
191
+ role: keyData.role,
192
+ authMethod: 'api-key'
193
+ };
194
+ }
195
+ }
196
+
197
+ // Check MCP service token
198
+ if (!user && serviceToken) {
199
+ const isValidServiceToken = validateServiceToken(serviceToken);
200
+ if (isValidServiceToken) {
201
+ user = {
202
+ id: 'mcp-service',
203
+ name: 'MCP Service',
204
+ role: MCP_SERVICE_ROLES.MCP_CLIENT,
205
+ authMethod: 'service-token'
206
+ };
207
+ }
208
+ }
209
+
210
+ if (!user) {
211
+ console.log(`[AUTH-DEBUG] ❌ Authentication failed - no valid credentials found`);
212
+ return res.status(401).json({
213
+ error: 'Authentication required',
214
+ message: 'Please provide a valid API key, Bearer token, or service token',
215
+ debug: {
216
+ clientIp: req.ip,
217
+ userAgent: req.headers['user-agent'],
218
+ timestamp: new Date().toISOString()
219
+ }
220
+ });
221
+ }
222
+
223
+ console.log(`[AUTH-DEBUG] ✅ Authentication successful for user:`, {
224
+ id: user.id,
225
+ name: user.name,
226
+ role: user.role,
227
+ authMethod: user.authMethod
228
+ });
229
+
230
+ req.user = user;
231
+ next();
232
+ }
233
+
234
+ // Authorization middleware
235
+ export function authorize(requiredPermission) {
236
+ return (req, res, next) => {
237
+ const user = req.user;
238
+ if (!user) {
239
+ return res.status(401).json({ error: 'Authentication required' });
240
+ }
241
+
242
+ // Get permissions from role-based mapping
243
+ const rolePermissions = ROLE_PERMISSIONS[user.role] || [];
244
+
245
+ // Also check explicit permissions on user object (for localhost bypass, etc.)
246
+ const explicitPermissions = user.permissions || [];
247
+
248
+ // Combine both permission sources
249
+ const userPermissions = [...new Set([...rolePermissions, ...explicitPermissions])];
250
+
251
+ if (!userPermissions.includes(requiredPermission)) {
252
+ return res.status(403).json({
253
+ error: 'Insufficient permissions',
254
+ required: requiredPermission,
255
+ userRole: user.role,
256
+ userPermissions: userPermissions
257
+ });
258
+ }
259
+
260
+ next();
261
+ };
262
+ }
263
+
264
+ // Admin-only middleware
265
+ export function requireAdmin(req, res, next) {
266
+ if (!req.user || req.user.role !== ROLES.ADMIN) {
267
+ return res.status(403).json({
268
+ error: 'Admin access required'
269
+ });
270
+ }
271
+ next();
272
+ }
273
+
274
+ // API key management functions
275
+ export function listApiKeys() {
276
+ return apiKeys.map(key => ({
277
+ id: key.id,
278
+ name: key.name,
279
+ role: key.role,
280
+ createdAt: key.createdAt,
281
+ lastUsed: key.lastUsed,
282
+ active: key.active
283
+ }));
284
+ }
285
+
286
+ export function revokeApiKey(keyId) {
287
+ const key = apiKeys.find(k => k.id === keyId);
288
+ if (key) {
289
+ key.active = false;
290
+ saveApiKeys();
291
+ return true;
292
+ }
293
+ return false;
294
+ }
295
+
296
+ export function activateApiKey(keyId) {
297
+ const key = apiKeys.find(k => k.id === keyId);
298
+ if (key) {
299
+ key.active = true;
300
+ saveApiKeys();
301
+ return true;
302
+ }
303
+ return false;
304
+ }
305
+
306
+ export function deleteApiKey(keyId) {
307
+ const index = apiKeys.findIndex(k => k.id === keyId);
308
+ if (index !== -1) {
309
+ apiKeys.splice(index, 1);
310
+ saveApiKeys();
311
+ return true;
312
+ }
313
+ return false;
314
+ }
315
+
316
+ // Initialize with default admin key if no keys exist
317
+ if (apiKeys.length === 0) {
318
+ const defaultAdmin = generateApiKey('Default Admin', ROLES.ADMIN);
319
+ console.log('=== Chrome Debug Security Initialization ===');
320
+ console.log('Generated default admin API key:');
321
+ console.log(`API Key: ${defaultAdmin.apiKey}`);
322
+ console.log('Please save this key securely and create additional keys as needed.');
323
+ console.log('============================================');
324
+ }
325
+
326
+ // Generate MCP service token
327
+ export function generateServiceToken() {
328
+ const serviceToken = `mcp_${uuidv4().replace(/-/g, '')}`;
329
+ return serviceToken;
330
+ }
331
+
332
+ // Validate MCP service token
333
+ function validateServiceToken(token) {
334
+ const expectedToken = process.env.MCP_SERVICE_TOKEN;
335
+ if (!expectedToken) {
336
+ console.warn('MCP_SERVICE_TOKEN not set. Service authentication disabled.');
337
+ return false;
338
+ }
339
+ return token === expectedToken;
340
+ }
341
+
342
+ // Initialize MCP service token if not set
343
+ function initializeMcpServiceToken() {
344
+ if (!process.env.MCP_SERVICE_TOKEN) {
345
+ const serviceToken = generateServiceToken();
346
+ process.env.MCP_SERVICE_TOKEN = serviceToken;
347
+ console.log('=== ChromeDebug MCP Service Token ===');
348
+ console.log(`Generated MCP service token: ${serviceToken}`);
349
+ console.log('This token allows MCP clients to access recordings.');
350
+ console.log('======================================');
351
+
352
+ // Optionally save to .env file for persistence
353
+ try {
354
+ const envPath = path.join(__dirname, '../../.env');
355
+ const envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
356
+
357
+ if (!envContent.includes('MCP_SERVICE_TOKEN=')) {
358
+ const newEnvContent = envContent + (envContent.endsWith('\n') ? '' : '\n') + `MCP_SERVICE_TOKEN=${serviceToken}\n`;
359
+ fs.writeFileSync(envPath, newEnvContent);
360
+ console.log('MCP service token saved to .env file');
361
+ }
362
+ } catch (error) {
363
+ console.log('Could not save MCP service token to .env file:', error.message);
364
+ }
365
+ }
366
+ }
367
+
368
+ // Initialize MCP service token on module load
369
+ initializeMcpServiceToken();
370
+
371
+ export { ROLES, PERMISSIONS, ROLE_PERMISSIONS, MCP_SERVICE_ROLES, validateServiceToken };
@@ -0,0 +1,267 @@
1
+ import helmet from 'helmet';
2
+ import rateLimit from 'express-rate-limit';
3
+ import cors from 'cors';
4
+
5
+ // Environment configuration
6
+ const isDevelopment = process.env.NODE_ENV === 'development';
7
+ const isProduction = process.env.NODE_ENV === 'production';
8
+
9
+ // Trusted origins configuration
10
+ const trustedOrigins = [
11
+ // Chrome extension origins
12
+ 'chrome-extension://*',
13
+ // VS Code extension
14
+ 'vscode-webview://*',
15
+ // Local development (always allowed for Chrome extension compatibility)
16
+ 'http://localhost:*',
17
+ 'http://127.0.0.1:*',
18
+ 'http://0.0.0.0:*',
19
+ // Add production origins from environment
20
+ ...(process.env.TRUSTED_ORIGINS ? process.env.TRUSTED_ORIGINS.split(',') : [])
21
+ ];
22
+
23
+ // CORS configuration
24
+ export const corsOptions = {
25
+ origin: function (origin, callback) {
26
+ // Allow requests with no origin (mobile apps, Postman, etc.)
27
+ if (!origin) return callback(null, true);
28
+
29
+ // Check if origin matches any trusted pattern
30
+ const isAllowed = trustedOrigins.some(pattern => {
31
+ if (pattern.includes('*')) {
32
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
33
+ return regex.test(origin);
34
+ }
35
+ return pattern === origin;
36
+ });
37
+
38
+ if (isAllowed) {
39
+ callback(null, true);
40
+ } else {
41
+ console.warn(`CORS: Blocked origin ${origin}`);
42
+ callback(new Error('Not allowed by CORS policy'));
43
+ }
44
+ },
45
+ credentials: true,
46
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
47
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Requested-With'],
48
+ exposedHeaders: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset']
49
+ };
50
+
51
+ // Security headers configuration
52
+ export const securityHeaders = helmet({
53
+ contentSecurityPolicy: {
54
+ directives: {
55
+ defaultSrc: ["'self'"],
56
+ scriptSrc: ["'self'", "'unsafe-inline'"],
57
+ styleSrc: ["'self'", "'unsafe-inline'"],
58
+ imgSrc: ["'self'", "data:", "blob:"],
59
+ connectSrc: ["'self'", "ws:", "wss:"],
60
+ fontSrc: ["'self'"],
61
+ objectSrc: ["'none'"],
62
+ mediaSrc: ["'self'", "blob:"],
63
+ frameSrc: ["'none'"]
64
+ }
65
+ },
66
+ crossOriginEmbedderPolicy: false, // Allow Chrome extension integration
67
+ crossOriginOpenerPolicy: false,
68
+ crossOriginResourcePolicy: { policy: "cross-origin" },
69
+ hsts: {
70
+ maxAge: 31536000,
71
+ includeSubDomains: true,
72
+ preload: true
73
+ }
74
+ });
75
+
76
+ // Rate limiting configuration
77
+ export const createRateLimit = (options = {}) => {
78
+ return rateLimit({
79
+ windowMs: options.windowMs || 15 * 60 * 1000, // 15 minutes
80
+ max: options.max || 100, // Limit each IP to 100 requests per windowMs
81
+ message: {
82
+ error: 'Too many requests',
83
+ retryAfter: options.windowMs || 15 * 60 * 1000
84
+ },
85
+ standardHeaders: true,
86
+ legacyHeaders: false,
87
+ // Use user ID for authenticated requests, default for unauthenticated
88
+ keyGenerator: (req) => {
89
+ if (req.user?.id) {
90
+ return `user:${req.user.id}`;
91
+ }
92
+ // Let express-rate-limit handle IP address properly (including IPv6)
93
+ return undefined;
94
+ },
95
+ skip: (req) => {
96
+ // Skip rate limiting for certain endpoints in development
97
+ if (isDevelopment && req.path === '/chromedebug/status') {
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ });
103
+ };
104
+
105
+ // Different rate limits for different endpoint types
106
+ export const rateLimits = {
107
+ // General API rate limit
108
+ general: createRateLimit({
109
+ windowMs: 15 * 60 * 1000, // 15 minutes
110
+ max: 1000 // 1000 requests per 15 minutes
111
+ }),
112
+
113
+ // Authentication endpoints
114
+ auth: createRateLimit({
115
+ windowMs: 15 * 60 * 1000, // 15 minutes
116
+ max: 10 // 10 auth attempts per 15 minutes
117
+ }),
118
+
119
+ // File upload endpoints
120
+ upload: createRateLimit({
121
+ windowMs: 5 * 60 * 1000, // 5 minutes
122
+ max: 20 // 20 uploads per 5 minutes
123
+ }),
124
+
125
+ // Chrome control endpoints (more restrictive)
126
+ chromeControl: createRateLimit({
127
+ windowMs: 1 * 60 * 1000, // 1 minute
128
+ max: 60 // 60 control actions per minute
129
+ }),
130
+
131
+ // Status endpoints (less restrictive)
132
+ status: createRateLimit({
133
+ windowMs: 1 * 60 * 1000, // 1 minute
134
+ max: 100 // 100 status checks per minute
135
+ })
136
+ };
137
+
138
+ // Request size limits
139
+ export const requestSizeLimits = {
140
+ // Standard JSON requests
141
+ json: '10mb',
142
+
143
+ // Form data (for file uploads)
144
+ urlencoded: '10mb',
145
+
146
+ // Large uploads (frame batches, screenshots)
147
+ largeUpload: '50mb'
148
+ };
149
+
150
+ // Security audit logging
151
+ export function securityLogger(req, res, next) {
152
+ const start = Date.now();
153
+
154
+ // Log sensitive operations
155
+ const sensitiveEndpoints = [
156
+ '/chromedebug/workflow-recording',
157
+ '/chromedebug/frame-batch',
158
+ '/chromedebug/evaluate',
159
+ '/chromedebug/launch',
160
+ '/chromedebug/navigate'
161
+ ];
162
+
163
+ const isSensitive = sensitiveEndpoints.some(endpoint =>
164
+ req.path.startsWith(endpoint)
165
+ );
166
+
167
+ if (isSensitive) {
168
+ console.log(`[Security] ${req.method} ${req.path} - User: ${req.user?.name || 'unauthenticated'} - IP: ${req.ip}`);
169
+ }
170
+
171
+ // Track response time for monitoring
172
+ res.on('finish', () => {
173
+ const duration = Date.now() - start;
174
+ if (duration > 5000) { // Log slow requests
175
+ console.warn(`[Performance] Slow request: ${req.method} ${req.path} - ${duration}ms`);
176
+ }
177
+ });
178
+
179
+ next();
180
+ }
181
+
182
+ // Request validation middleware
183
+ export function validateRequest(schema) {
184
+ return (req, res, next) => {
185
+ const { error, value } = schema.validate(req.body, {
186
+ abortEarly: false,
187
+ stripUnknown: true
188
+ });
189
+
190
+ if (error) {
191
+ return res.status(400).json({
192
+ error: 'Validation failed',
193
+ details: error.details.map(detail => ({
194
+ field: detail.path.join('.'),
195
+ message: detail.message
196
+ }))
197
+ });
198
+ }
199
+
200
+ req.validatedBody = value;
201
+ next();
202
+ };
203
+ }
204
+
205
+ // Error handling for security middleware
206
+ export function securityErrorHandler(err, req, res, next) {
207
+ // CORS errors
208
+ if (err.message.includes('CORS')) {
209
+ return res.status(403).json({
210
+ error: 'CORS policy violation',
211
+ message: 'Origin not allowed'
212
+ });
213
+ }
214
+
215
+ // Rate limiting errors
216
+ if (err.status === 429) {
217
+ return res.status(429).json({
218
+ error: 'Rate limit exceeded',
219
+ message: 'Too many requests, please try again later'
220
+ });
221
+ }
222
+
223
+ // Security headers errors
224
+ if (err.code === 'EBADCSRFTOKEN') {
225
+ return res.status(403).json({
226
+ error: 'Invalid CSRF token'
227
+ });
228
+ }
229
+
230
+ // Default security error response
231
+ console.error('[Security Error]', err);
232
+ res.status(500).json({
233
+ error: 'Security error',
234
+ message: 'An internal security error occurred'
235
+ });
236
+ }
237
+
238
+ // IP whitelisting (if needed)
239
+ export function createIPWhitelist(allowedIPs = []) {
240
+ return (req, res, next) => {
241
+ if (allowedIPs.length === 0) {
242
+ return next(); // No restrictions if no IPs specified
243
+ }
244
+
245
+ const clientIP = req.ip || req.connection.remoteAddress;
246
+
247
+ if (!allowedIPs.includes(clientIP)) {
248
+ console.warn(`[Security] Blocked IP: ${clientIP}`);
249
+ return res.status(403).json({
250
+ error: 'IP not whitelisted'
251
+ });
252
+ }
253
+
254
+ next();
255
+ };
256
+ }
257
+
258
+ export default {
259
+ corsOptions,
260
+ securityHeaders,
261
+ rateLimits,
262
+ requestSizeLimits,
263
+ securityLogger,
264
+ validateRequest,
265
+ securityErrorHandler,
266
+ createIPWhitelist
267
+ };