Skip to main content
Glama
OWASPZAPIntegration.ts11.3 kB
/** * OWASP ZAP Integration * * Integrates with OWASP ZAP (Zed Attack Proxy) for automated security testing */ // import path from 'path'; import { spawn, ChildProcess } from 'child_process'; import { promises as fs } from 'fs'; import axios, { AxiosInstance } from 'axios'; import { Logger } from '../../utils/logger'; import { SecurityTestResult, SecurityTestIntegration, OWASPZAPConfig, SecurityScanOptions, SecurityTestSeverity, // VulnerabilityReport, } from '../types'; export class OWASPZAPIntegration implements SecurityTestIntegration { public readonly name = 'OWASP ZAP'; public readonly version = '2.14.0'; public configValid = false; private logger: Logger; private config: OWASPZAPConfig; private zapProcess: ChildProcess | undefined; private zapClient?: AxiosInstance; private baseUrl: string; // private zap: any; // private isInitialized = false; constructor(config: OWASPZAPConfig) { this.config = config; this.logger = new Logger({ component: 'OWASPZAPIntegration' }); this.baseUrl = `http://${config.host || 'localhost'}:${config.port || 8080}`; } async initialize(): Promise<void> { this.logger.info('Initializing OWASP ZAP integration'); if (!this.config.enabled) { this.logger.info('OWASP ZAP integration disabled'); return; } try { // Check if ZAP is already running const isRunning = await this.checkZAPRunning(); if (!isRunning) { await this.startZAP(); } // Initialize ZAP client this.zapClient = axios.create({ baseURL: this.baseUrl, timeout: 30000, headers: { 'X-ZAP-API-Key': this.config.apiKey || '' } }); // Wait for ZAP to be ready await this.waitForZAPReady(); this.configValid = true; this.logger.info('OWASP ZAP integration initialized successfully'); } catch (error) { this.logger.error('Failed to initialize OWASP ZAP integration', undefined, error instanceof Error ? error : new Error(String(error))); throw error; } } async checkAvailability(): Promise<boolean> { try { if (!this.config.enabled) { return false; } // Check if ZAP binary exists if (this.config.zapPath) { try { await fs.access(this.config.zapPath); } catch { this.logger.warn(`ZAP binary not found at ${this.config.zapPath}`); return false; } } // Check if ZAP is running or can be started return await this.checkZAPRunning() || await this.canStartZAP(); } catch (error) { this.logger.error('Error checking OWASP ZAP availability', undefined, error instanceof Error ? error : new Error(String(error))); return false; } } async runScan(options?: SecurityScanOptions): Promise<SecurityTestResult[]> { this.logger.info('Starting OWASP ZAP security scan'); if (!this.zapClient) { throw new Error('OWASP ZAP not initialized'); } const scanOptions = { targetUrl: options?.targetUrl || this.getDefaultTarget(), scanType: options?.scanType || 'baseline', maxTime: options?.maxTime || this.config.maxScanTime || 300000, // 5 minutes ...options }; const results: SecurityTestResult[] = []; const startTime = Date.now(); try { // Start spider scan first if (scanOptions.scanType !== 'passive') { await this.runSpiderScan(scanOptions.targetUrl); } // Run passive scan await this.runPassiveScan(); // Run active scan if requested if (scanOptions.scanType === 'active') { await this.runActiveScan(scanOptions.targetUrl); } // Get results const alerts = await this.getAlerts(); for (const alert of alerts) { results.push(this.convertAlertToResult(alert, startTime)); } this.logger.info(`OWASP ZAP scan completed. Found ${results.length} security issues`); return results; } catch (error) { this.logger.error('OWASP ZAP scan failed', undefined, error instanceof Error ? error : new Error(String(error))); throw error; } } async runAll(): Promise<SecurityTestResult[]> { return await this.runScan(); } async runTest(testId: string): Promise<SecurityTestResult> { // ZAP doesn't support individual test execution const results = await this.runAll(); const result = results.find(r => r.id === testId); if (!result) { throw new Error(`Test ${testId} not found`); } return result; } async cleanup(): Promise<void> { this.logger.info('Cleaning up OWASP ZAP integration'); try { if (this.zapProcess) { this.zapProcess.kill('SIGTERM'); this.zapProcess = undefined; } } catch (error) { this.logger.error('Error cleaning up OWASP ZAP', undefined, error instanceof Error ? error : new Error(String(error))); } } private async checkZAPRunning(): Promise<boolean> { try { const response = await axios.get(`${this.baseUrl}/JSON/core/view/version/`, { timeout: 5000 }); return response.status === 200; } catch { return false; } } private async canStartZAP(): Promise<boolean> { if (!this.config.zapPath) { // Try to find ZAP in common locations const commonPaths = [ '/usr/share/zaproxy/zap.sh', '/opt/zaproxy/zap.sh', 'zap.sh', // In PATH 'zap.exe' // Windows ]; for (const zapPath of commonPaths) { try { await fs.access(zapPath); this.config.zapPath = zapPath; return true; } catch { // Continue to next path } } return false; } try { await fs.access(this.config.zapPath); return true; } catch { return false; } } private async startZAP(): Promise<void> { if (!this.config.zapPath) { throw new Error('ZAP path not configured'); } this.logger.info(`Starting ZAP at ${this.config.zapPath}`); const zapArgs = [ '-daemon', '-port', (this.config.port || 8080).toString(), '-host', this.config.host || 'localhost' ]; if (this.config.apiKey) { zapArgs.push('-config', `api.key=${this.config.apiKey}`); } this.zapProcess = spawn(this.config.zapPath, zapArgs, { detached: true, stdio: 'pipe' }); // Handle process events this.zapProcess.on('error', (error) => { this.logger.error('ZAP process error', undefined, error); }); this.zapProcess.on('exit', (code) => { this.logger.info(`ZAP process exited with code ${code}`); this.zapProcess = undefined; }); // Wait for ZAP to start await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('ZAP startup timeout')); }, 30000); const checkStartup = setInterval(async () => { if (await this.checkZAPRunning()) { clearInterval(checkStartup); clearTimeout(timeout); resolve(undefined); } }, 1000); }); } private async waitForZAPReady(): Promise<void> { const maxRetries = 30; let retries = 0; while (retries < maxRetries) { try { await this.zapClient!.get('/JSON/core/view/version/'); return; } catch (error) { retries++; if (retries >= maxRetries) { throw new Error('ZAP not ready after maximum retries'); } await new Promise(resolve => setTimeout(resolve, 1000)); } } } private async runSpiderScan(targetUrl: string): Promise<void> { this.logger.info(`Starting spider scan on ${targetUrl}`); const response = await this.zapClient!.get('/JSON/spider/action/scan/', { params: { url: targetUrl, recurse: 'true' } }); const scanId = response.data.scan; // Wait for spider to complete while (true) { const statusResponse = await this.zapClient!.get('/JSON/spider/view/status/', { params: { scanId } }); const progress = parseInt(statusResponse.data.status); if (progress >= 100) { break; } await new Promise(resolve => setTimeout(resolve, 2000)); } this.logger.info('Spider scan completed'); } private async runPassiveScan(): Promise<void> { this.logger.info('Running passive scan'); // Passive scan runs automatically, just wait for completion while (true) { const response = await this.zapClient!.get('/JSON/pscan/view/recordsToScan/'); const recordsToScan = parseInt(response.data.recordsToScan); if (recordsToScan === 0) { break; } await new Promise(resolve => setTimeout(resolve, 2000)); } this.logger.info('Passive scan completed'); } private async runActiveScan(targetUrl: string): Promise<void> { this.logger.info(`Starting active scan on ${targetUrl}`); const response = await this.zapClient!.get('/JSON/ascan/action/scan/', { params: { url: targetUrl, recurse: 'true' } }); const scanId = response.data.scan; // Wait for active scan to complete while (true) { const statusResponse = await this.zapClient!.get('/JSON/ascan/view/status/', { params: { scanId } }); const progress = parseInt(statusResponse.data.status); if (progress >= 100) { break; } await new Promise(resolve => setTimeout(resolve, 5000)); } this.logger.info('Active scan completed'); } private async getAlerts(): Promise<any[]> { const response = await this.zapClient!.get('/JSON/core/view/alerts/'); return response.data.alerts || []; } private convertAlertToResult(alert: any, startTime: number): SecurityTestResult { const severity = this.mapZAPRiskToSeverity(alert.risk); return { id: `zap-${alert.pluginId}-${alert.alert}`, name: alert.alert, suite: 'owasp-zap', status: 'failed', // ZAP alerts are always failures severity, description: alert.description, details: alert.solution, evidence: { type: 'request', content: JSON.stringify({ url: alert.url, method: alert.method, param: alert.param, attack: alert.attack, evidence: alert.evidence }), encoding: 'utf8', mimeType: 'application/json' }, remediation: alert.solution, duration: Date.now() - startTime, timestamp: new Date(), metadata: { pluginId: alert.pluginId, cweid: alert.cweid, wascid: alert.wascid, reference: alert.reference, confidence: alert.confidence } }; } private mapZAPRiskToSeverity(risk: string): SecurityTestSeverity { switch (risk.toLowerCase()) { case 'high': return 'high'; case 'medium': return 'medium'; case 'low': return 'low'; case 'informational': return 'low'; default: return 'medium'; } } private getDefaultTarget(): string { return 'http://localhost:3000'; // Default target } }

Latest Blog Posts

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/learnwithcc/tally-mcp'

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