UNPKG

browser-connect-mcp

Version:

MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance

347 lines 16.5 kB
export class SecurityScanner { sensitivePatterns = [ { pattern: /password[\s=:]+["']?([^"'\s]+)/gi, type: 'password' }, { pattern: /api[_-]?key[\s=:]+["']?([a-z0-9\-_]+)/gi, type: 'api_key' }, { pattern: /token[\s=:]+["']?([a-z0-9\-_]+)/gi, type: 'token' }, { pattern: /secret[\s=:]+["']?([a-z0-9\-_]+)/gi, type: 'secret' }, { pattern: /private[_-]?key[\s=:]+["']?([a-z0-9\-_]+)/gi, type: 'private_key' }, { pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, type: 'email' }, { pattern: /\b(?:\d{4}[\s-]?){3}\d{4}\b/g, type: 'credit_card' }, { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, type: 'ssn' }, ]; securityHeaders = [ 'Strict-Transport-Security', 'X-Content-Type-Options', 'X-Frame-Options', 'X-XSS-Protection', 'Content-Security-Policy', 'Referrer-Policy', 'Permissions-Policy', ]; scanForVulnerabilities(messages, requests, responses) { const issues = []; const responseMap = new Map(responses.map(r => [r.requestId, r])); // 1. Scan console for sensitive data issues.push(...this.scanConsoleForSensitiveData(messages)); // 2. Scan network for insecure transmissions issues.push(...this.scanNetworkSecurity(requests, responseMap)); // 3. Check for CORS misconfigurations issues.push(...this.scanCORSIssues(requests, responseMap)); // 4. Check for XSS risks issues.push(...this.scanXSSRisks(messages)); // 5. Check for exposed errors with sensitive info issues.push(...this.scanExposedErrors(messages)); // 6. Check for missing security headers issues.push(...this.scanSecurityHeaders(requests, responseMap)); // Sort by severity return issues.sort((a, b) => { const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; return severityOrder[a.severity] - severityOrder[b.severity]; }); } scanConsoleForSensitiveData(messages) { const issues = []; for (const message of messages) { for (const { pattern, type } of this.sensitivePatterns) { const matches = message.text.match(pattern); if (matches) { issues.push({ id: `console-sensitive-${message.timestamp}-${type}`, type: 'sensitive-data', severity: type === 'password' || type === 'private_key' ? 'critical' : 'high', title: `${type.replace(/_/g, ' ')} exposed in console`, description: `Sensitive data (${type}) was found in console logs. This could expose credentials to anyone with access to the browser console.`, evidence: { source: 'console', data: { message: message.text.substring(0, 200), timestamp: message.timestamp, level: message.level, }, location: message.url, }, recommendations: [ `Remove all ${type} from console logs`, 'Use environment variables for sensitive configuration', 'Implement proper logging that filters sensitive data', 'Use a secure credential management system', ], references: [ 'https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure', ], }); } } } return issues; } scanNetworkSecurity(requests, _responseMap) { const issues = []; for (const request of requests) { // Check for sensitive data in URLs for (const { pattern, type } of this.sensitivePatterns) { if (pattern.test(request.url)) { issues.push({ id: `url-sensitive-${request.requestId}-${type}`, type: 'sensitive-data', severity: 'high', title: `${type} exposed in URL`, description: `Sensitive data (${type}) found in URL. URLs are often logged and cached, making this a security risk.`, evidence: { source: 'network', data: { url: request.url, method: request.method, }, }, recommendations: [ 'Move sensitive data to request body or headers', 'Use POST requests for sensitive operations', 'Implement proper URL parameter encryption', ], }); } } // Check for insecure transmission if (request.url.startsWith('http://') && !request.url.includes('localhost')) { const hassSensitiveData = request.postData && this.containsSensitiveData(request.postData) || this.containsSensitiveHeaders(request.headers); if (hassSensitiveData) { issues.push({ id: `insecure-transmission-${request.requestId}`, type: 'insecure-transmission', severity: 'critical', title: 'Sensitive data transmitted over insecure connection', description: 'Sensitive data is being transmitted over HTTP instead of HTTPS, making it vulnerable to interception.', evidence: { source: 'network', data: { url: request.url, method: request.method, hasSensitiveData: true, }, }, recommendations: [ 'Use HTTPS for all data transmission', 'Implement HSTS (HTTP Strict Transport Security)', 'Redirect all HTTP traffic to HTTPS', ], references: [ 'https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure', ], }); } } } return issues; } scanCORSIssues(requests, responseMap) { const issues = []; for (const request of requests) { const response = responseMap.get(request.requestId); if (!response) continue; const corsHeader = response.headers['access-control-allow-origin'] || response.headers['Access-Control-Allow-Origin']; if (corsHeader === '*') { const hasCredentials = response.headers['access-control-allow-credentials'] === 'true' || response.headers['Access-Control-Allow-Credentials'] === 'true'; if (hasCredentials) { issues.push({ id: `cors-wildcard-credentials-${request.requestId}`, type: 'cors-misconfiguration', severity: 'high', title: 'CORS wildcard with credentials', description: 'The server allows any origin (*) with credentials, which is a security risk.', evidence: { source: 'network', data: { url: request.url, headers: { 'Access-Control-Allow-Origin': corsHeader, 'Access-Control-Allow-Credentials': 'true', }, }, }, recommendations: [ 'Specify exact allowed origins instead of using wildcard', 'Implement proper CORS origin validation', 'Review credential requirements for cross-origin requests', ], references: [ 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS', ], }); } } } return issues; } scanXSSRisks(messages) { const issues = []; const xssPatterns = [ /<script[^>]*>.*<\/script>/gi, /javascript:/gi, /on\w+\s*=/gi, // Event handlers like onclick= /eval\s*\(/gi, /innerHTML\s*=/gi, /document\.write/gi, ]; for (const message of messages) { if (message.level === 'error') { for (const pattern of xssPatterns) { if (pattern.test(message.text)) { issues.push({ id: `xss-risk-${message.timestamp}`, type: 'xss-risk', severity: 'high', title: 'Potential XSS vulnerability detected', description: 'Console error contains patterns that suggest potential XSS vulnerability.', evidence: { source: 'console', data: { message: message.text.substring(0, 200), timestamp: message.timestamp, }, location: message.url, }, recommendations: [ 'Sanitize all user input before rendering', 'Use textContent instead of innerHTML', 'Implement Content Security Policy (CSP)', 'Avoid using eval() and similar functions', ], references: [ 'https://owasp.org/www-community/attacks/xss/', ], }); break; } } } } return issues; } scanExposedErrors(messages) { const issues = []; const sensitivePathPatterns = [ /\/home\/\w+/gi, /\/users\/\w+/gi, /c:\\users\\\w+/gi, /\/var\/www/gi, /node_modules/gi, /\.env/gi, ]; const errorMessages = messages.filter(m => m.level === 'error'); for (const message of errorMessages) { // Check for exposed file paths for (const pattern of sensitivePathPatterns) { if (pattern.test(message.text)) { issues.push({ id: `exposed-path-${message.timestamp}`, type: 'exposed-error', severity: 'medium', title: 'Sensitive path information exposed in error', description: 'Error messages contain file system paths that could help attackers understand the server structure.', evidence: { source: 'console', data: { message: message.text.substring(0, 200), timestamp: message.timestamp, }, }, recommendations: [ 'Implement custom error pages that hide technical details', 'Log detailed errors server-side only', 'Use generic error messages for client-side display', ], }); break; } } // Check for stack traces with sensitive info if (message.stackTrace && JSON.stringify(message.stackTrace).length > 500) { issues.push({ id: `exposed-stacktrace-${message.timestamp}`, type: 'exposed-error', severity: 'low', title: 'Detailed stack trace exposed', description: 'Detailed stack traces can reveal application structure and libraries used.', evidence: { source: 'console', data: { message: message.text.substring(0, 100), stackTraceLength: JSON.stringify(message.stackTrace).length, }, }, recommendations: [ 'Disable detailed error messages in production', 'Use error tracking services that keep details server-side', ], }); } } return issues; } scanSecurityHeaders(requests, responseMap) { const issues = []; const checkedDomains = new Set(); for (const request of requests) { const response = responseMap.get(request.requestId); if (!response || response.status >= 400) continue; // Only check each domain once const domain = new URL(request.url).hostname; if (checkedDomains.has(domain)) continue; checkedDomains.add(domain); // Skip localhost if (domain === 'localhost' || domain === '127.0.0.1') continue; const missingHeaders = []; for (const header of this.securityHeaders) { const headerLower = header.toLowerCase(); const hasHeader = Object.keys(response.headers).some(h => h.toLowerCase() === headerLower); if (!hasHeader) { missingHeaders.push(header); } } if (missingHeaders.length > 0) { issues.push({ id: `missing-headers-${domain}`, type: 'missing-headers', severity: missingHeaders.includes('Strict-Transport-Security') ? 'high' : 'medium', title: `Missing security headers on ${domain}`, description: `Important security headers are missing, potentially exposing the application to various attacks.`, evidence: { source: 'network', data: { domain, missingHeaders, url: request.url, }, }, recommendations: [ 'Add Strict-Transport-Security header for HTTPS enforcement', 'Add X-Content-Type-Options: nosniff to prevent MIME sniffing', 'Add X-Frame-Options to prevent clickjacking', 'Implement Content Security Policy (CSP)', 'Add other security headers as appropriate', ], references: [ 'https://owasp.org/www-project-secure-headers/', ], }); } } return issues; } containsSensitiveData(text) { return this.sensitivePatterns.some(({ pattern }) => pattern.test(text)); } containsSensitiveHeaders(headers) { const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token']; return Object.keys(headers).some(h => sensitiveHeaders.includes(h.toLowerCase()) && headers[h]?.length > 0); } } //# sourceMappingURL=security-scanner.js.map