@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,405 @@
1
+ // PII Redactor for Chrome Debug Full Data Recording
2
+ // Sanitizes sensitive information before data leaves the client
3
+
4
+ class PIIRedactor {
5
+ constructor() {
6
+ // Regex patterns for common PII
7
+ this.patterns = {
8
+ email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
9
+ phone: /(\+?\d{1,3}[-.\s]?)?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}/g,
10
+ ssn: /\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b/g,
11
+ creditCard: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
12
+ ipAddress: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
13
+ apiKey: /\b[A-Za-z0-9]{32,}\b/g, // Common API key pattern
14
+ jwt: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
15
+ password: /password["\s]*[:=]["\s]*[^",\s}]+/gi,
16
+ token: /token["\s]*[:=]["\s]*[^",\s}]+/gi,
17
+ secret: /secret["\s]*[:=]["\s]*[^",\s}]+/gi
18
+ };
19
+
20
+ // CSS selectors for sensitive elements
21
+ this.sensitiveSelectors = [
22
+ 'input[type="password"]',
23
+ 'input[type="email"]',
24
+ 'input[name*="password"]',
25
+ 'input[name*="pwd"]',
26
+ 'input[name*="email"]',
27
+ 'input[name*="ssn"]',
28
+ 'input[name*="credit"]',
29
+ 'input[name*="card"]',
30
+ 'input[name*="cvv"]',
31
+ 'input[name*="account"]',
32
+ 'input[name*="routing"]',
33
+ '[data-sensitive="true"]',
34
+ '[data-pii="true"]'
35
+ ];
36
+
37
+ // Custom redaction rules (user-configurable)
38
+ this.customRules = [];
39
+ this.loadCustomRules();
40
+ }
41
+
42
+ async loadCustomRules() {
43
+ if (chrome?.storage?.local) {
44
+ chrome.storage.local.get(['piiCustomRules'], (result) => {
45
+ if (result.piiCustomRules) {
46
+ this.customRules = result.piiCustomRules;
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ redactText(text) {
53
+ if (!text || typeof text !== 'string') return text;
54
+
55
+ let redacted = text;
56
+
57
+ // Apply standard patterns
58
+ for (const [type, pattern] of Object.entries(this.patterns)) {
59
+ redacted = redacted.replace(pattern, (match) => {
60
+ return this.maskValue(match, type);
61
+ });
62
+ }
63
+
64
+ // Apply custom rules
65
+ for (const rule of this.customRules) {
66
+ if (rule.pattern && rule.enabled) {
67
+ try {
68
+ const regex = new RegExp(rule.pattern, rule.flags || 'gi');
69
+ redacted = redacted.replace(regex, (match) => {
70
+ return this.maskValue(match, 'custom');
71
+ });
72
+ } catch (error) {
73
+ console.error('[PIIRedactor] Invalid custom regex:', error);
74
+ }
75
+ }
76
+ }
77
+
78
+ return redacted;
79
+ }
80
+
81
+ redactObject(obj, depth = 0, maxDepth = 10) {
82
+ if (depth > maxDepth) return '[MAX_DEPTH_EXCEEDED]';
83
+ if (!obj) return obj;
84
+
85
+ // Handle different types
86
+ if (typeof obj === 'string') {
87
+ return this.redactText(obj);
88
+ }
89
+
90
+ if (Array.isArray(obj)) {
91
+ return obj.map(item => this.redactObject(item, depth + 1, maxDepth));
92
+ }
93
+
94
+ if (typeof obj === 'object') {
95
+ const redacted = {};
96
+
97
+ for (const [key, value] of Object.entries(obj)) {
98
+ // Check if key indicates sensitive data
99
+ if (this.isSensitiveKey(key)) {
100
+ redacted[key] = this.maskValue(value, 'sensitive');
101
+ } else {
102
+ redacted[key] = this.redactObject(value, depth + 1, maxDepth);
103
+ }
104
+ }
105
+
106
+ return redacted;
107
+ }
108
+
109
+ return obj;
110
+ }
111
+
112
+ redactDOMSnapshot(domData) {
113
+ if (!domData) return domData;
114
+
115
+ const redacted = { ...domData };
116
+
117
+ // Redact form values
118
+ if (redacted.forms) {
119
+ redacted.forms = redacted.forms.map(form => {
120
+ const redactedForm = { ...form };
121
+
122
+ if (redactedForm.fields) {
123
+ redactedForm.fields = redactedForm.fields.map(field => {
124
+ if (this.isFieldSensitive(field)) {
125
+ return {
126
+ ...field,
127
+ value: this.maskValue(field.value, 'form-field')
128
+ };
129
+ }
130
+ return field;
131
+ });
132
+ }
133
+
134
+ return redactedForm;
135
+ });
136
+ }
137
+
138
+ // Redact text content
139
+ if (redacted.textContent) {
140
+ redacted.textContent = this.redactText(redacted.textContent);
141
+ }
142
+
143
+ // Redact HTML if present
144
+ if (redacted.html) {
145
+ redacted.html = this.redactHTML(redacted.html);
146
+ }
147
+
148
+ return redacted;
149
+ }
150
+
151
+ redactHTML(html) {
152
+ if (!html || typeof html !== 'string') return html;
153
+
154
+ // Create a temporary DOM element to parse HTML
155
+ const tempDiv = document.createElement('div');
156
+ tempDiv.innerHTML = html;
157
+
158
+ // Find and redact sensitive elements
159
+ const sensitiveElements = tempDiv.querySelectorAll(this.sensitiveSelectors.join(','));
160
+
161
+ sensitiveElements.forEach(element => {
162
+ if (element.value) {
163
+ element.value = this.maskValue(element.value, 'html-input');
164
+ }
165
+ if (element.textContent) {
166
+ element.textContent = this.redactText(element.textContent);
167
+ }
168
+ });
169
+
170
+ // Redact all text nodes
171
+ const walker = document.createTreeWalker(
172
+ tempDiv,
173
+ NodeFilter.SHOW_TEXT,
174
+ null,
175
+ false
176
+ );
177
+
178
+ const textNodes = [];
179
+ while (walker.nextNode()) {
180
+ textNodes.push(walker.currentNode);
181
+ }
182
+
183
+ textNodes.forEach(node => {
184
+ node.textContent = this.redactText(node.textContent);
185
+ });
186
+
187
+ return tempDiv.innerHTML;
188
+ }
189
+
190
+ redactNetworkRequest(request) {
191
+ if (!request) return request;
192
+
193
+ const redacted = { ...request };
194
+
195
+ // Redact headers
196
+ if (redacted.headers) {
197
+ redacted.headers = this.redactHeaders(redacted.headers);
198
+ }
199
+
200
+ // Redact body
201
+ if (redacted.body) {
202
+ if (typeof redacted.body === 'string') {
203
+ redacted.body = this.redactText(redacted.body);
204
+ } else {
205
+ redacted.body = this.redactObject(redacted.body);
206
+ }
207
+ }
208
+
209
+ // Redact URL query parameters
210
+ if (redacted.url) {
211
+ redacted.url = this.redactURL(redacted.url);
212
+ }
213
+
214
+ return redacted;
215
+ }
216
+
217
+ redactHeaders(headers) {
218
+ if (!headers) return headers;
219
+
220
+ const sensitiveHeaders = [
221
+ 'authorization',
222
+ 'cookie',
223
+ 'x-api-key',
224
+ 'x-auth-token',
225
+ 'x-csrf-token',
226
+ 'x-access-token'
227
+ ];
228
+
229
+ const redacted = { ...headers };
230
+
231
+ for (const [key, value] of Object.entries(redacted)) {
232
+ const lowerKey = key.toLowerCase();
233
+
234
+ if (sensitiveHeaders.some(h => lowerKey.includes(h))) {
235
+ redacted[key] = this.maskValue(value, 'header');
236
+ } else {
237
+ redacted[key] = this.redactText(value);
238
+ }
239
+ }
240
+
241
+ return redacted;
242
+ }
243
+
244
+ redactURL(url) {
245
+ if (!url || typeof url !== 'string') return url;
246
+
247
+ try {
248
+ const urlObj = new URL(url);
249
+
250
+ // Redact query parameters
251
+ const params = new URLSearchParams(urlObj.search);
252
+ const redactedParams = new URLSearchParams();
253
+
254
+ for (const [key, value] of params) {
255
+ if (this.isSensitiveKey(key)) {
256
+ redactedParams.set(key, this.maskValue(value, 'url-param'));
257
+ } else {
258
+ redactedParams.set(key, this.redactText(value));
259
+ }
260
+ }
261
+
262
+ urlObj.search = redactedParams.toString();
263
+ return urlObj.toString();
264
+ } catch (error) {
265
+ // If URL parsing fails, just redact as text
266
+ return this.redactText(url);
267
+ }
268
+ }
269
+
270
+ isSensitiveKey(key) {
271
+ if (!key || typeof key !== 'string') return false;
272
+
273
+ const lowerKey = key.toLowerCase();
274
+ const sensitiveKeywords = [
275
+ 'password',
276
+ 'pwd',
277
+ 'pass',
278
+ 'secret',
279
+ 'token',
280
+ 'key',
281
+ 'api',
282
+ 'auth',
283
+ 'credit',
284
+ 'card',
285
+ 'cvv',
286
+ 'ssn',
287
+ 'account',
288
+ 'routing',
289
+ 'pin'
290
+ ];
291
+
292
+ return sensitiveKeywords.some(keyword => lowerKey.includes(keyword));
293
+ }
294
+
295
+ isFieldSensitive(field) {
296
+ if (!field) return false;
297
+
298
+ // Check field type
299
+ if (field.type === 'password' || field.type === 'email') {
300
+ return true;
301
+ }
302
+
303
+ // Check field name
304
+ if (field.name && this.isSensitiveKey(field.name)) {
305
+ return true;
306
+ }
307
+
308
+ // Check field ID
309
+ if (field.id && this.isSensitiveKey(field.id)) {
310
+ return true;
311
+ }
312
+
313
+ return false;
314
+ }
315
+
316
+ maskValue(value, type = 'generic') {
317
+ if (!value) return value;
318
+
319
+ const str = String(value);
320
+ const len = str.length;
321
+
322
+ // Different masking strategies based on type
323
+ switch (type) {
324
+ case 'email':
325
+ const atIndex = str.indexOf('@');
326
+ if (atIndex > 1) {
327
+ return str[0] + '*'.repeat(atIndex - 1) + str.substring(atIndex);
328
+ }
329
+ break;
330
+
331
+ case 'creditCard':
332
+ if (len >= 12) {
333
+ return '*'.repeat(len - 4) + str.substring(len - 4);
334
+ }
335
+ break;
336
+
337
+ case 'phone':
338
+ if (len >= 10) {
339
+ return '*'.repeat(len - 4) + str.substring(len - 4);
340
+ }
341
+ break;
342
+
343
+ case 'ssn':
344
+ return '***-**-' + (len >= 4 ? str.substring(len - 4) : '****');
345
+
346
+ case 'password':
347
+ case 'token':
348
+ case 'secret':
349
+ case 'apiKey':
350
+ case 'jwt':
351
+ return '[REDACTED]';
352
+
353
+ default:
354
+ // Generic masking - show first and last character if long enough
355
+ if (len > 4) {
356
+ return str[0] + '*'.repeat(len - 2) + str[len - 1];
357
+ }
358
+ }
359
+
360
+ // Fallback to complete masking
361
+ return '*'.repeat(len);
362
+ }
363
+
364
+ // Main redaction method for all data types
365
+ redactData(data, dataType) {
366
+ switch (dataType) {
367
+ case 'dom_mutation':
368
+ case 'dom_snapshot':
369
+ return this.redactDOMSnapshot(data);
370
+
371
+ case 'network_request':
372
+ return this.redactNetworkRequest(data);
373
+
374
+ case 'console_log':
375
+ if (data.message) {
376
+ data.message = this.redactText(data.message);
377
+ }
378
+ return data;
379
+
380
+ case 'variable_state':
381
+ if (data.variables) {
382
+ data.variables = this.redactObject(data.variables);
383
+ }
384
+ return data;
385
+
386
+ case 'execution_trace':
387
+ if (data.arguments) {
388
+ data.arguments = this.redactObject(data.arguments);
389
+ }
390
+ if (data.return_value) {
391
+ data.return_value = this.redactObject(data.return_value);
392
+ }
393
+ return data;
394
+
395
+ default:
396
+ // Generic object redaction
397
+ return this.redactObject(data);
398
+ }
399
+ }
400
+ }
401
+
402
+ // Export for use in content and background scripts
403
+ if (typeof module !== 'undefined' && module.exports) {
404
+ module.exports = PIIRedactor;
405
+ }