Skip to main content
Glama
zap.ts•14.3 kB
import axios, { AxiosInstance } from 'axios'; export interface ZAPScanResult { success: boolean; data?: any; error?: string; } export interface ZAPSpiderResult { scanId: string; progress: number; status: string; } export interface ZAPActiveScanResult { scanId: string; progress: number; status: string; } export interface ZAPAlert { id: string; name: string; risk: 'Informational' | 'Low' | 'Medium' | 'High' | 'Critical'; confidence: 'False Positive' | 'Low' | 'Medium' | 'High' | 'Confirmed'; url: string; param?: string; attack?: string; evidence?: string; description?: string; solution?: string; reference?: string; } export interface ZAPContext { id: number; name: string; urls: string[]; } /** * ZAP API Integration * Connects to OWASP ZAP via REST API */ export class ZAPClient { private client: AxiosInstance; private baseURL: string; private apiKey?: string; constructor(baseURL: string = 'http://localhost:8081', apiKey?: string) { this.baseURL = baseURL.replace(/\/$/, ''); this.apiKey = apiKey || process.env.ZAP_API_KEY; this.client = axios.create({ baseURL: `${this.baseURL}/JSON`, timeout: 30000, }); this.client.interceptors.request.use((config) => { config.params = { ...(config.params || {}), ...(this.apiKey ? { apikey: this.apiKey } : {}), }; return config; }); } /** * Check if ZAP is running and accessible */ async healthCheck(): Promise<ZAPScanResult> { try { const response = await this.client.get('/core/view/version/'); return { success: true, data: { version: response.data.version, status: 'running', }, }; } catch (error: any) { return { success: false, error: error.message || 'ZAP is not accessible', }; } } /** * Start a spider scan */ async startSpider(url: string, maxChildren?: number, recurse?: boolean, contextName?: string): Promise<ZAPScanResult> { try { const params: any = { url }; if (maxChildren) params.maxChildren = maxChildren; if (recurse !== undefined) params.recurse = recurse; if (contextName) params.contextName = contextName; const response = await this.client.get('/spider/action/scan/', { params }); // Handle different response formats const scanId = response.data.scan || response.data.scanId || response.data; if (!scanId && scanId !== 0) { throw new Error('No scan ID returned from ZAP'); } return { success: true, data: { scanId: scanId.toString(), }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to start spider scan', }; } } /** * Get spider scan status */ async getSpiderStatus(scanId: string): Promise<ZAPScanResult> { try { const response = await this.client.get('/spider/view/status/', { params: { scanId: scanId.toString() }, }); return { success: true, data: { scanId, progress: parseInt(response.data.status || '0') || 0, status: response.data.status === '100' ? 'completed' : 'running', }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get spider status', }; } } /** * Start an active scan */ async startActiveScan(url: string, recurse?: boolean, inScopeOnly?: boolean, scanPolicyName?: string, method?: string, postData?: string): Promise<ZAPScanResult> { try { const params: any = { url }; if (recurse !== undefined) params.recurse = recurse; if (inScopeOnly !== undefined) params.inScopeOnly = inScopeOnly; if (scanPolicyName) params.scanPolicyName = scanPolicyName; if (method) params.method = method; if (postData) params.postData = postData; const response = await this.client.get('/ascan/action/scan/', { params }); // Handle different response formats const scanId = response.data.scan || response.data.scanId || response.data; if (!scanId && scanId !== 0) { throw new Error('No scan ID returned from ZAP'); } return { success: true, data: { scanId: scanId.toString(), }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to start active scan', }; } } /** * Get active scan status */ async getActiveScanStatus(scanId: string): Promise<ZAPScanResult> { try { const response = await this.client.get('/ascan/view/status/', { params: { scanId: scanId.toString() }, }); return { success: true, data: { scanId, progress: parseInt(response.data.status || '0') || 0, status: response.data.status === '100' ? 'completed' : 'running', }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get active scan status', }; } } /** * Get all alerts */ async getAlerts(baseURL?: string, start?: number, count?: number, riskId?: string): Promise<ZAPScanResult> { try { const params: any = {}; if (baseURL) params.baseurl = baseURL; if (start !== undefined) params.start = start; if (count !== undefined) params.count = count; if (riskId) params.riskId = riskId; const response = await this.client.get('/alert/view/alerts/', { params }); // Handle both array format and object format responses let alertsData: any[] = []; if (Array.isArray(response.data.alerts)) { alertsData = response.data.alerts; } else if (response.data.alerts && typeof response.data.alerts === 'object') { // If alerts is an object, convert to array alertsData = Object.values(response.data.alerts); } else if (Array.isArray(response.data)) { // Some ZAP versions return alerts directly as array alertsData = response.data; } const alerts: ZAPAlert[] = alertsData.map((alert: any) => ({ id: alert.pluginId?.toString() || alert.id?.toString() || '', name: alert.alert || alert.name || 'Unknown Alert', risk: this.mapRisk(alert.risk || alert.riskString || 'Informational'), confidence: this.mapConfidence(alert.confidence || alert.confidenceString || 'Low'), url: alert.url || '', param: alert.param || undefined, attack: alert.attack || undefined, evidence: alert.evidence || undefined, description: alert.description || undefined, solution: alert.solution || undefined, reference: alert.reference || undefined, })); return { success: true, data: { alerts, count: alerts.length, }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get alerts', }; } } /** * Get alerts summary (counts by risk level) */ async getAlertsSummary(baseURL?: string): Promise<ZAPScanResult> { try { const params: any = {}; if (baseURL) params.baseurl = baseURL; const response = await this.client.get('/alert/view/alertCountsByRisk/', { params }); // Parse the response - ZAP returns alertCountsByRisk with risk levels as keys const summaryData = response.data.alertCountsByRisk || response.data; return { success: true, data: { informational: summaryData['0'] || summaryData.Informational || 0, low: summaryData['1'] || summaryData.Low || 0, medium: summaryData['2'] || summaryData.Medium || 0, high: summaryData['3'] || summaryData.High || 0, critical: summaryData['4'] || summaryData.Critical || 0, raw: summaryData, }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get alerts summary', }; } } /** * Send a custom HTTP request through ZAP * Note: ZAP requires proxying through ZAP proxy. Use /core/action/sendRequest/ for direct API calls */ async sendRequest(url: string, method: string = 'GET', headers?: Record<string, string>, body?: string): Promise<ZAPScanResult> { try { const params: any = { url, method }; if (headers) { // ZAP expects headers as a string in format "HeaderName: HeaderValue" params.headers = Object.entries(headers) .filter(([k]) => k.toLowerCase() !== 'content-length') // Remove content-length, ZAP will add it .map(([k, v]) => `${k}: ${v}`) .join('\n'); } if (body) params.body = body; // Try /core/action/sendRequest/ first, fallback to /httpSender/action/sendRequest/ try { const response = await this.client.get('/core/action/sendRequest/', { params }); return { success: true, data: response.data, }; } catch (coreError: any) { // Fallback to httpSender endpoint const response = await this.client.get('/httpSender/action/sendRequest/', { params }); return { success: true, data: response.data, }; } } catch (error: any) { return { success: false, error: error.message || 'Failed to send request', }; } } /** * Create a context for scanning */ async createContext(contextName: string): Promise<ZAPScanResult> { try { const response = await this.client.get('/context/action/newContext/', { params: { contextName }, }); return { success: true, data: { contextId: parseInt(response.data.contextId), name: contextName, }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to create context', }; } } /** * Include URL in context * Note: ZAP API accepts contextName or contextId. We use contextName here. */ async includeInContext(contextName: string, regex: string): Promise<ZAPScanResult> { try { // First try with contextName, if that fails and we have a numeric contextName, try as contextId try { const response = await this.client.get('/context/action/includeInContext/', { params: { contextName, regex }, }); return { success: true, data: response.data, }; } catch (error: any) { // If contextName fails and it's numeric, try as contextId if (!isNaN(Number(contextName))) { const response = await this.client.get('/context/action/includeInContext/', { params: { contextId: contextName, regex }, }); return { success: true, data: response.data, }; } throw error; } } catch (error: any) { return { success: false, error: error.message || 'Failed to include URL in context', }; } } /** * Set scan policy */ async setScanPolicy(policyName: string, attackStrength?: string, alertThreshold?: string): Promise<ZAPScanResult> { try { const params: any = { scanPolicyName: policyName }; if (attackStrength) params.attackStrength = attackStrength; if (alertThreshold) params.alertThreshold = alertThreshold; const response = await this.client.get('/ascan/action/addScanPolicy/', { params }); return { success: true, data: response.data, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to set scan policy', }; } } /** * Get sites tree (discovered URLs) */ async getSites(): Promise<ZAPScanResult> { try { const response = await this.client.get('/core/view/sites/'); return { success: true, data: { sites: response.data.sites || [], }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get sites', }; } } /** * Get URLs for a specific site */ async getUrls(baseURL?: string): Promise<ZAPScanResult> { try { const params: any = {}; if (baseURL) params.baseurl = baseURL; const response = await this.client.get('/core/view/urls/', { params }); return { success: true, data: { urls: response.data.urls || [], }, }; } catch (error: any) { return { success: false, error: error.message || 'Failed to get URLs', }; } } /** * Map ZAP risk string to our risk enum */ private mapRisk(risk: string): ZAPAlert['risk'] { const riskMap: Record<string, ZAPAlert['risk']> = { '0': 'Informational', '1': 'Low', '2': 'Medium', '3': 'High', '4': 'Critical', 'Informational': 'Informational', 'Low': 'Low', 'Medium': 'Medium', 'High': 'High', 'Critical': 'Critical', }; return riskMap[risk] || 'Informational'; } /** * Map ZAP confidence string to our confidence enum */ private mapConfidence(confidence: string): ZAPAlert['confidence'] { const confMap: Record<string, ZAPAlert['confidence']> = { '0': 'False Positive', '1': 'Low', '2': 'Medium', '3': 'High', '4': 'Confirmed', 'False Positive': 'False Positive', 'Low': 'Low', 'Medium': 'Medium', 'High': 'High', 'Confirmed': 'Confirmed', }; return confMap[confidence] || 'Low'; } } // Singleton instance let zapClient: ZAPClient | null = null; export function initZAP(baseURL?: string, apiKey?: string): ZAPClient { if (!zapClient) { zapClient = new ZAPClient( baseURL || process.env.ZAP_URL || 'http://localhost:8081', apiKey || process.env.ZAP_API_KEY ); } return zapClient; } export function getZAPClient(): ZAPClient | null { return zapClient; }

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