Skip to main content
Glama
zap-proxy.ts•12 kB
import { IncomingMessage, ServerResponse } from 'http'; import { ZAPClient, ZAPAlert } from './zap'; import { saveTestResult } from './postgres'; export interface ProxyRequest { method: string; url: string; headers: Record<string, string>; body?: string; timestamp: number; } export interface ProxyResponse { statusCode: number; headers: Record<string, string>; body: string; timestamp: number; } export interface EnhancedFinding { zapAlert?: ZAPAlert; customFinding?: { type: string; severity: 'low' | 'medium' | 'high' | 'critical'; confidence: number; description: string; evidence: string; url: string; param?: string; }; correlationScore: number; aiScore?: number; verified: boolean; } /** * MCP Proxy Layer * Intercepts requests, enhances them, routes through ZAP, and adds AI intelligence */ export class MCPProxyLayer { private zapClient: ZAPClient; private requestHistory: ProxyRequest[] = []; private responseHistory: Map<string, ProxyResponse> = new Map(); private customFindings: EnhancedFinding[] = []; constructor(zapClient: ZAPClient) { this.zapClient = zapClient; } /** * Process incoming request through proxy */ async processRequest( method: string, url: string, headers: Record<string, string>, body?: string ): Promise<{ request: ProxyRequest; response?: ProxyResponse; findings: EnhancedFinding[] }> { const request: ProxyRequest = { method, url, headers, body, timestamp: Date.now(), }; // Store request this.requestHistory.push(request); const requestId = `${method}_${url}_${Date.now()}`; // Send through ZAP const zapResponse = await this.zapClient.sendRequest(url, method, headers, body); // Extract response if available let response: ProxyResponse | undefined; if (zapResponse.success && zapResponse.data) { // Parse ZAP response format response = { statusCode: zapResponse.data.statusCode || 200, headers: zapResponse.data.headers || {}, body: zapResponse.data.body || '', timestamp: Date.now(), }; this.responseHistory.set(requestId, response); } // Analyze for custom findings const customFindings = await this.analyzeRequest(request, response); // Get ZAP alerts for this URL const zapAlerts = await this.getZAPAlertsForURL(url); // Correlate findings const findings = this.correlateFindings(zapAlerts, customFindings, url); return { request, response, findings, }; } /** * Analyze request for custom vulnerabilities */ private async analyzeRequest( request: ProxyRequest, response?: ProxyResponse ): Promise<EnhancedFinding[]> { const findings: EnhancedFinding[] = []; // 1. Check for sensitive parameters const sensitiveParams = this.detectSensitiveParameters(request); if (sensitiveParams.length > 0) { findings.push({ customFinding: { type: 'sensitive_parameter_exposure', severity: 'medium', confidence: 0.7, description: `Sensitive parameters detected: ${sensitiveParams.join(', ')}`, evidence: JSON.stringify(request), url: request.url, param: sensitiveParams.join(', '), }, correlationScore: 0.7, verified: false, }); } // 2. Check for authentication bypass patterns if (response && this.detectAuthBypass(request, response)) { findings.push({ customFinding: { type: 'authentication_bypass', severity: 'high', confidence: 0.6, description: 'Potential authentication bypass detected', evidence: `Request: ${JSON.stringify(request)}, Response: ${JSON.stringify(response)}`, url: request.url, }, correlationScore: 0.6, verified: false, }); } // 3. Check for IDOR patterns if (this.detectIDOR(request)) { findings.push({ customFinding: { type: 'idor', severity: 'high', confidence: 0.5, description: 'Potential IDOR vulnerability - user-controlled resource ID', evidence: JSON.stringify(request), url: request.url, }, correlationScore: 0.5, verified: false, }); } // 4. Check for business logic flaws if (this.detectBusinessLogicFlaw(request)) { findings.push({ customFinding: { type: 'business_logic_flaw', severity: 'medium', confidence: 0.6, description: 'Potential business logic vulnerability detected', evidence: JSON.stringify(request), url: request.url, }, correlationScore: 0.6, verified: false, }); } // 5. Check for missing security headers if (response && this.detectMissingSecurityHeaders(response)) { findings.push({ customFinding: { type: 'missing_security_headers', severity: 'low', confidence: 0.8, description: 'Missing security headers (CSP, HSTS, X-Frame-Options, etc.)', evidence: JSON.stringify(response.headers), url: request.url, }, correlationScore: 0.8, verified: true, }); } return findings; } /** * Detect sensitive parameters in request */ private detectSensitiveParameters(request: ProxyRequest): string[] { const sensitiveKeywords = [ 'admin', 'role', 'permission', 'token', 'auth', 'password', 'secret', 'key', 'api_key', 'access_token', 'userId', 'user_id', 'id', 'amount', 'price', 'discount', 'privilege', 'privileges', 'isAdmin', 'is_admin', ]; const found: string[] = []; const urlLower = request.url.toLowerCase(); const bodyLower = request.body?.toLowerCase() || ''; for (const keyword of sensitiveKeywords) { if (urlLower.includes(keyword) || bodyLower.includes(keyword)) { found.push(keyword); } } return found; } /** * Detect potential authentication bypass */ private detectAuthBypass(request: ProxyRequest, response: ProxyResponse): boolean { // Check if request without auth headers gets 200 OK if (response.statusCode === 200 && !request.headers['Authorization'] && !request.headers['Cookie']) { // Check if URL looks like it should require auth const protectedPaths = ['/api/admin', '/api/user', '/api/account', '/dashboard', '/admin']; return protectedPaths.some(path => request.url.includes(path)); } return false; } /** * Detect IDOR patterns */ private detectIDOR(request: ProxyRequest): boolean { // Check for numeric IDs in URL or body const idPattern = /\/(\d+)\//g; const urlMatches = request.url.match(idPattern); const bodyMatches = request.body?.match(idPattern); if (urlMatches || bodyMatches) { // Check if it's a user-related endpoint const userPatterns = ['/user/', '/account/', '/profile/', '/order/', '/transaction/']; return userPatterns.some(pattern => request.url.includes(pattern)); } return false; } /** * Detect business logic flaws */ private detectBusinessLogicFlaw(request: ProxyRequest): boolean { // Check for suspicious operations const suspiciousPatterns = [ { method: 'POST', path: '/refund', param: 'amount' }, { method: 'PUT', path: '/price', param: 'amount' }, { method: 'DELETE', path: '/order', param: 'id' }, { method: 'POST', path: '/transfer', param: 'amount' }, ]; return suspiciousPatterns.some(pattern => { return request.method === pattern.method && request.url.includes(pattern.path) && (request.url.includes(pattern.param) || request.body?.includes(pattern.param)); }); } /** * Detect missing security headers */ private detectMissingSecurityHeaders(response: ProxyResponse): boolean { const requiredHeaders = [ 'Content-Security-Policy', 'X-Frame-Options', 'X-Content-Type-Options', 'Strict-Transport-Security', ]; const missing = requiredHeaders.filter(header => !response.headers[header]); return missing.length > 0; } /** * Get ZAP alerts for a specific URL */ private async getZAPAlertsForURL(url: string): Promise<ZAPAlert[]> { const result = await this.zapClient.getAlerts(url); if (result.success && result.data) { return result.data.alerts || []; } return []; } /** * Correlate ZAP alerts with custom findings */ private correlateFindings( zapAlerts: ZAPAlert[], customFindings: EnhancedFinding[], url: string ): EnhancedFinding[] { const correlated: EnhancedFinding[] = []; // Add ZAP alerts for (const alert of zapAlerts) { correlated.push({ zapAlert: alert, correlationScore: this.calculateCorrelationScore(alert), verified: alert.confidence === 'High' || alert.confidence === 'Confirmed', }); } // Add custom findings for (const finding of customFindings) { // Check if ZAP already found something similar const similarZAPAlert = zapAlerts.find(alert => this.areFindingsSimilar(alert, finding.customFinding) ); if (similarZAPAlert) { // Merge findings correlated.push({ zapAlert: similarZAPAlert, customFinding: finding.customFinding, correlationScore: Math.max( this.calculateCorrelationScore(similarZAPAlert), finding.correlationScore ), verified: true, }); } else { // Add as new finding correlated.push(finding); } } // Calculate AI scores return correlated.map(finding => ({ ...finding, aiScore: this.calculateAIScore(finding), })); } /** * Calculate correlation score for a finding */ private calculateCorrelationScore(alert: ZAPAlert): number { const riskScores: Record<ZAPAlert['risk'], number> = { 'Informational': 0.2, 'Low': 0.4, 'Medium': 0.6, 'High': 0.8, 'Critical': 1.0, }; const confidenceScores: Record<ZAPAlert['confidence'], number> = { 'False Positive': 0.0, 'Low': 0.3, 'Medium': 0.6, 'High': 0.8, 'Confirmed': 1.0, }; return (riskScores[alert.risk] || 0) * (confidenceScores[alert.confidence] || 0); } /** * Check if findings are similar */ private areFindingsSimilar(zapAlert: ZAPAlert, customFinding?: EnhancedFinding['customFinding']): boolean { if (!customFinding) return false; // Simple similarity check based on URL and type return zapAlert.url === customFinding.url && this.mapVulnerabilityTypes(zapAlert.name) === customFinding.type; } /** * Map ZAP alert names to our vulnerability types */ private mapVulnerabilityTypes(zapAlertName: string): string { const name = zapAlertName.toLowerCase(); if (name.includes('xss')) return 'xss'; if (name.includes('sql')) return 'sqli'; if (name.includes('csrf')) return 'csrf'; if (name.includes('idor')) return 'idor'; if (name.includes('auth')) return 'authentication_bypass'; return 'unknown'; } /** * Calculate AI-powered score */ private calculateAIScore(finding: EnhancedFinding): number { let score = finding.correlationScore; // Boost score if both ZAP and custom finding agree if (finding.zapAlert && finding.customFinding) { score *= 1.3; } // Boost score if verified if (finding.verified) { score *= 1.2; } return Math.min(score, 1.0); } /** * Get all findings */ getFindings(): EnhancedFinding[] { return this.customFindings; } /** * Clear findings */ clearFindings(): void { this.customFindings = []; this.requestHistory = []; this.responseHistory.clear(); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/telmon95/VulneraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server