@ricardodeazambuja/browser-mcp-server 1.0.3 → 1.4.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 (43) hide show
  1. package/CHANGELOG-v1.3.0.md +42 -0
  2. package/CHANGELOG-v1.4.0.md +8 -0
  3. package/README.md +271 -45
  4. package/package.json +11 -10
  5. package/plugins/.gitkeep +0 -0
  6. package/src/.gitkeep +0 -0
  7. package/src/browser.js +152 -0
  8. package/src/cdp.js +58 -0
  9. package/src/index.js +126 -0
  10. package/src/tools/.gitkeep +0 -0
  11. package/src/tools/console.js +139 -0
  12. package/src/tools/docs.js +1611 -0
  13. package/src/tools/index.js +60 -0
  14. package/src/tools/info.js +139 -0
  15. package/src/tools/interaction.js +126 -0
  16. package/src/tools/keyboard.js +27 -0
  17. package/src/tools/media.js +264 -0
  18. package/src/tools/mouse.js +104 -0
  19. package/src/tools/navigation.js +72 -0
  20. package/src/tools/network.js +552 -0
  21. package/src/tools/pages.js +149 -0
  22. package/src/tools/performance.js +517 -0
  23. package/src/tools/security.js +470 -0
  24. package/src/tools/storage.js +467 -0
  25. package/src/tools/system.js +196 -0
  26. package/src/utils.js +131 -0
  27. package/tests/.gitkeep +0 -0
  28. package/tests/fixtures/.gitkeep +0 -0
  29. package/tests/fixtures/test-media.html +35 -0
  30. package/tests/fixtures/test-network.html +48 -0
  31. package/tests/fixtures/test-performance.html +61 -0
  32. package/tests/fixtures/test-security.html +33 -0
  33. package/tests/fixtures/test-storage.html +76 -0
  34. package/tests/run-all.js +50 -0
  35. package/{test-browser-automation.js → tests/test-browser-automation.js} +44 -5
  36. package/{test-mcp.js → tests/test-mcp.js} +9 -4
  37. package/tests/test-media-tools.js +168 -0
  38. package/tests/test-network.js +212 -0
  39. package/tests/test-performance.js +254 -0
  40. package/tests/test-security.js +203 -0
  41. package/tests/test-storage.js +192 -0
  42. package/CHANGELOG-v1.0.2.md +0 -126
  43. package/browser-mcp-server-playwright.js +0 -792
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Security Testing Tools (CDP-based)
3
+ * Security headers, TLS certificates, mixed content, CSP violations
4
+ */
5
+
6
+ const { connectToBrowser } = require('../browser');
7
+ const { getCDPSession } = require('../cdp');
8
+ const { debugLog } = require('../utils');
9
+
10
+ // Local state for security tools
11
+ let cspViolations = [];
12
+ let cspMonitoringActive = false;
13
+
14
+ const definitions = [
15
+ {
16
+ name: 'browser_sec_get_security_headers',
17
+ description: 'Inspect security-related HTTP headers (see browser_docs)',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {},
21
+ additionalProperties: false,
22
+ $schema: 'http://json-schema.org/draft-07/schema#'
23
+ }
24
+ },
25
+ {
26
+ name: 'browser_sec_get_certificate_info',
27
+ description: 'Get TLS/SSL certificate details for HTTPS sites (see browser_docs)',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {},
31
+ additionalProperties: false,
32
+ $schema: 'http://json-schema.org/draft-07/schema#'
33
+ }
34
+ },
35
+ {
36
+ name: 'browser_sec_detect_mixed_content',
37
+ description: 'Detect mixed content warnings (HTTPS page loading HTTP resources) (see browser_docs)',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {},
41
+ additionalProperties: false,
42
+ $schema: 'http://json-schema.org/draft-07/schema#'
43
+ }
44
+ },
45
+ {
46
+ name: 'browser_sec_start_csp_monitoring',
47
+ description: 'Monitor Content Security Policy violations (see browser_docs)',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {},
51
+ additionalProperties: false,
52
+ $schema: 'http://json-schema.org/draft-07/schema#'
53
+ }
54
+ },
55
+ {
56
+ name: 'browser_sec_get_csp_violations',
57
+ description: 'Get captured CSP violations (see browser_docs)',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: {},
61
+ additionalProperties: false,
62
+ $schema: 'http://json-schema.org/draft-07/schema#'
63
+ }
64
+ },
65
+ {
66
+ name: 'browser_sec_stop_csp_monitoring',
67
+ description: 'Stop CSP monitoring and clear violations (see browser_docs)',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {},
71
+ additionalProperties: false,
72
+ $schema: 'http://json-schema.org/draft-07/schema#'
73
+ }
74
+ }
75
+ ];
76
+
77
+ const handlers = {
78
+ browser_sec_get_security_headers: async (args) => {
79
+ try {
80
+ const { page } = await connectToBrowser();
81
+
82
+ // Get headers from HTTP response or meta tags
83
+ const securityData = await page.evaluate(async () => {
84
+ const result = {
85
+ headers: null,
86
+ metaTags: {},
87
+ protocol: window.location.protocol
88
+ };
89
+
90
+ // Try to get headers using fetch (works for HTTP/HTTPS)
91
+ if (window.location.protocol.startsWith('http')) {
92
+ try {
93
+ const res = await fetch(window.location.href, { method: 'HEAD' });
94
+ const headers = {};
95
+ for (let [key, value] of res.headers.entries()) {
96
+ headers[key] = value;
97
+ }
98
+ result.headers = headers;
99
+ } catch (e) {
100
+ // Fetch failed, will fall back to meta tags
101
+ }
102
+ }
103
+
104
+ // Read security-related meta tags (works for file:// and as fallback)
105
+ const metaTags = document.querySelectorAll('meta[http-equiv]');
106
+ metaTags.forEach(tag => {
107
+ const httpEquiv = tag.getAttribute('http-equiv').toLowerCase();
108
+ const content = tag.getAttribute('content');
109
+ if (httpEquiv && content) {
110
+ result.metaTags[httpEquiv] = content;
111
+ }
112
+ });
113
+
114
+ return result;
115
+ });
116
+
117
+ // Build security headers object from HTTP headers or meta tags
118
+ const securityHeaders = {
119
+ 'content-security-policy':
120
+ securityData.headers?.['content-security-policy'] ||
121
+ securityData.metaTags['content-security-policy'] ||
122
+ 'Not set',
123
+ 'strict-transport-security':
124
+ securityData.headers?.['strict-transport-security'] ||
125
+ 'Not set',
126
+ 'x-frame-options':
127
+ securityData.headers?.['x-frame-options'] ||
128
+ securityData.metaTags['x-frame-options'] ||
129
+ 'Not set',
130
+ 'x-content-type-options':
131
+ securityData.headers?.['x-content-type-options'] ||
132
+ 'Not set',
133
+ 'referrer-policy':
134
+ securityData.headers?.['referrer-policy'] ||
135
+ securityData.metaTags['referrer-policy'] ||
136
+ 'Not set',
137
+ 'permissions-policy':
138
+ securityData.headers?.['permissions-policy'] ||
139
+ 'Not set',
140
+ 'x-xss-protection':
141
+ securityData.headers?.['x-xss-protection'] ||
142
+ 'Not set (deprecated)'
143
+ };
144
+
145
+ const source = securityData.headers ? 'HTTP headers' : 'meta tags';
146
+
147
+ return {
148
+ content: [{
149
+ type: 'text',
150
+ text: `🔒 Security Headers (from ${source}):\n\n${JSON.stringify(securityHeaders, null, 2)}`
151
+ }]
152
+ };
153
+ } catch (error) {
154
+ debugLog(`Error in browser_sec_get_security_headers: ${error.message}`);
155
+ return {
156
+ content: [{
157
+ type: 'text',
158
+ text: `❌ Error: ${error.message}`
159
+ }],
160
+ isError: true
161
+ };
162
+ }
163
+ },
164
+
165
+ browser_sec_get_certificate_info: async (args) => {
166
+ try {
167
+ const { page } = await connectToBrowser();
168
+ const url = page.url();
169
+
170
+ if (!url.startsWith('https://')) {
171
+ return {
172
+ content: [{
173
+ type: 'text',
174
+ text: '⚠️ Certificate information only available for HTTPS sites.\n\nCurrent page is not using HTTPS.'
175
+ }]
176
+ };
177
+ }
178
+
179
+ const cdp = await getCDPSession();
180
+ await cdp.send('Security.enable');
181
+
182
+ // Get security state which includes certificate info
183
+ const securityState = await page.evaluate(async () => {
184
+ // Try to get security info from the page context
185
+ return {
186
+ url: window.location.href,
187
+ protocol: window.location.protocol
188
+ };
189
+ });
190
+
191
+ // Note: Getting detailed certificate info via CDP is complex
192
+ // as it requires monitoring security state changes during navigation
193
+ // For now, provide basic HTTPS validation info
194
+
195
+ const certInfo = {
196
+ url: url,
197
+ protocol: 'HTTPS',
198
+ secure: true,
199
+ note: 'Detailed certificate inspection requires monitoring during page load. For full certificate details, use browser DevTools Security panel.'
200
+ };
201
+
202
+ await cdp.send('Security.disable');
203
+
204
+ return {
205
+ content: [{
206
+ type: 'text',
207
+ text: `🔒 Certificate Information:\n\n${JSON.stringify(certInfo, null, 2)}\n\nNote: For detailed certificate information (issuer, expiry, subject), use:\n1. Chrome DevTools > Security panel\n2. Or start network monitoring before navigation to capture TLS details`
208
+ }]
209
+ };
210
+ } catch (error) {
211
+ debugLog(`CDP error in browser_sec_get_certificate_info: ${error.message}`);
212
+ return {
213
+ content: [{
214
+ type: 'text',
215
+ text: `❌ CDP Error: ${error.message}`
216
+ }],
217
+ isError: true
218
+ };
219
+ }
220
+ },
221
+
222
+ browser_sec_detect_mixed_content: async (args) => {
223
+ try {
224
+ const { page } = await connectToBrowser();
225
+ const url = page.url();
226
+
227
+ if (!url.startsWith('https://')) {
228
+ return {
229
+ content: [{
230
+ type: 'text',
231
+ text: '⚠️ Mixed content detection only applies to HTTPS pages.\n\nCurrent page is not using HTTPS.'
232
+ }]
233
+ };
234
+ }
235
+
236
+ // Detect mixed content by analyzing resources
237
+ const mixedContent = await page.evaluate(() => {
238
+ const issues = [];
239
+
240
+ // Check all loaded resources
241
+ performance.getEntriesByType('resource').forEach(entry => {
242
+ if (entry.name.startsWith('http://')) {
243
+ issues.push({
244
+ url: entry.name,
245
+ type: entry.initiatorType,
246
+ blocked: false
247
+ });
248
+ }
249
+ });
250
+
251
+ // Check scripts
252
+ document.querySelectorAll('script[src]').forEach(script => {
253
+ if (script.src.startsWith('http://')) {
254
+ issues.push({
255
+ url: script.src,
256
+ type: 'script',
257
+ blocked: true // Mixed scripts are usually blocked
258
+ });
259
+ }
260
+ });
261
+
262
+ // Check stylesheets
263
+ document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
264
+ if (link.href.startsWith('http://')) {
265
+ issues.push({
266
+ url: link.href,
267
+ type: 'stylesheet',
268
+ blocked: false
269
+ });
270
+ }
271
+ });
272
+
273
+ // Check images
274
+ document.querySelectorAll('img[src]').forEach(img => {
275
+ if (img.src.startsWith('http://')) {
276
+ issues.push({
277
+ url: img.src,
278
+ type: 'image',
279
+ blocked: false
280
+ });
281
+ }
282
+ });
283
+
284
+ return issues;
285
+ });
286
+
287
+ if (mixedContent.length === 0) {
288
+ return {
289
+ content: [{
290
+ type: 'text',
291
+ text: '✅ No mixed content detected.\n\nAll resources are loaded over HTTPS.'
292
+ }]
293
+ };
294
+ }
295
+
296
+ const summary = {
297
+ total: mixedContent.length,
298
+ blocked: mixedContent.filter(i => i.blocked).length,
299
+ issues: mixedContent.slice(0, 20)
300
+ };
301
+
302
+ return {
303
+ content: [{
304
+ type: 'text',
305
+ text: `⚠️ Mixed Content Detected:\n\n${JSON.stringify(summary, null, 2)}\n\nNote: Showing first 20 issues. Mixed content can be a security risk.`
306
+ }]
307
+ };
308
+ } catch (error) {
309
+ debugLog(`Error in browser_sec_detect_mixed_content: ${error.message}`);
310
+ return {
311
+ content: [{
312
+ type: 'text',
313
+ text: `❌ Error: ${error.message}`
314
+ }],
315
+ isError: true
316
+ };
317
+ }
318
+ },
319
+
320
+ browser_sec_start_csp_monitoring: async (args) => {
321
+ try {
322
+ if (cspMonitoringActive) {
323
+ return {
324
+ content: [{
325
+ type: 'text',
326
+ text: '⚠️ CSP monitoring is already active.\n\nUse browser_sec_get_csp_violations to view violations or browser_sec_stop_csp_monitoring to stop.'
327
+ }]
328
+ };
329
+ }
330
+
331
+ const cdp = await getCDPSession();
332
+ cspViolations = [];
333
+
334
+ // Enable Log domain to capture CSP violations
335
+ await cdp.send('Log.enable');
336
+
337
+ // Listen for log entries
338
+ cdp.on('Log.entryAdded', (params) => {
339
+ const entry = params.entry;
340
+
341
+ // CSP violations appear as console errors with specific text
342
+ if (entry.source === 'security' ||
343
+ (entry.text && entry.text.includes('Content Security Policy')) ||
344
+ (entry.text && entry.text.includes('CSP'))) {
345
+
346
+ cspViolations.push({
347
+ timestamp: new Date(entry.timestamp).toISOString(),
348
+ text: entry.text,
349
+ level: entry.level,
350
+ source: entry.source,
351
+ url: entry.url,
352
+ lineNumber: entry.lineNumber
353
+ });
354
+ }
355
+ });
356
+
357
+ cspMonitoringActive = true;
358
+
359
+ debugLog('Started CSP violation monitoring');
360
+
361
+ return {
362
+ content: [{
363
+ type: 'text',
364
+ text: '✅ CSP violation monitoring started\n\nCapturing Content Security Policy violations...\n\nUse browser_sec_get_csp_violations to view captured violations.'
365
+ }]
366
+ };
367
+ } catch (error) {
368
+ debugLog(`CDP error in browser_sec_start_csp_monitoring: ${error.message}`);
369
+ return {
370
+ content: [{
371
+ type: 'text',
372
+ text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- CDP session disconnected\n- Log domain not supported`
373
+ }],
374
+ isError: true
375
+ };
376
+ }
377
+ },
378
+
379
+ browser_sec_get_csp_violations: async (args) => {
380
+ try {
381
+ if (!cspMonitoringActive) {
382
+ return {
383
+ content: [{
384
+ type: 'text',
385
+ text: '⚠️ CSP monitoring is not active.\n\nUse browser_sec_start_csp_monitoring to start monitoring first.'
386
+ }]
387
+ };
388
+ }
389
+
390
+ if (cspViolations.length === 0) {
391
+ return {
392
+ content: [{
393
+ type: 'text',
394
+ text: 'No CSP violations detected yet.\n\nMonitoring is active - violations will appear if any occur.'
395
+ }]
396
+ };
397
+ }
398
+
399
+ const summary = {
400
+ total: cspViolations.length,
401
+ violations: cspViolations.map(v => ({
402
+ timestamp: v.timestamp,
403
+ message: v.text,
404
+ level: v.level,
405
+ source: v.url || 'unknown'
406
+ }))
407
+ };
408
+
409
+ return {
410
+ content: [{
411
+ type: 'text',
412
+ text: `⚠️ CSP Violations (${cspViolations.length}):\n\n${JSON.stringify(summary, null, 2)}`
413
+ }]
414
+ };
415
+ } catch (error) {
416
+ debugLog(`Error in browser_sec_get_csp_violations: ${error.message}`);
417
+ return {
418
+ content: [{
419
+ type: 'text',
420
+ text: `❌ Error: ${error.message}`
421
+ }],
422
+ isError: true
423
+ };
424
+ }
425
+ },
426
+
427
+ browser_sec_stop_csp_monitoring: async (args) => {
428
+ try {
429
+ if (!cspMonitoringActive) {
430
+ return {
431
+ content: [{
432
+ type: 'text',
433
+ text: '⚠️ CSP monitoring is not active.'
434
+ }]
435
+ };
436
+ }
437
+
438
+ const cdp = await getCDPSession();
439
+ await cdp.send('Log.disable');
440
+
441
+ // Remove listener
442
+ cdp.removeAllListeners('Log.entryAdded');
443
+
444
+ const count = cspViolations.length;
445
+ cspViolations = [];
446
+ cspMonitoringActive = false;
447
+
448
+ debugLog('Stopped CSP violation monitoring');
449
+
450
+ return {
451
+ content: [{
452
+ type: 'text',
453
+ text: `✅ CSP monitoring stopped\n\nCaptured ${count} violations.\nData has been cleared.`
454
+ }]
455
+ };
456
+ } catch (error) {
457
+ cspMonitoringActive = false;
458
+ debugLog(`CDP error in browser_sec_stop_csp_monitoring: ${error.message}`);
459
+ return {
460
+ content: [{
461
+ type: 'text',
462
+ text: `❌ CDP Error: ${error.message}\n\nMonitoring has been stopped.`
463
+ }],
464
+ isError: true
465
+ };
466
+ }
467
+ }
468
+ };
469
+
470
+ module.exports = { definitions, handlers };