@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.
- package/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- 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
|
+
}
|