Skip to main content
Glama

MCP Shamash

pentest-scanner.ts12.9 kB
// import * as url from 'url'; // Uncomment if URL parsing needed import type { ScanRequest, ScanResult, Finding } from '../types/index.js'; import type { BoundaryEnforcer } from '../boundaries/enforcer.js'; import { DockerOrchestrator, type ScannerConfig } from './docker-orchestrator.js'; export class PentestScanner { private orchestrator: DockerOrchestrator; constructor(private boundaryEnforcer: BoundaryEnforcer) { const projectScope = this.boundaryEnforcer.getProjectScope(); if (!projectScope) { throw new Error('Project scope not initialized'); } this.orchestrator = new DockerOrchestrator(projectScope); } async scan(request: ScanRequest): Promise<ScanResult> { const scanId = this.generateScanId(); const startTime = Date.now(); // Validate URL boundaries const validation = await this.boundaryEnforcer.validateUrl(request.target); if (!validation.allowed) { throw new Error(`URL boundary violation: ${validation.reason}`); } console.error(`Starting pentest scan: ${scanId}`); const allFindings: Finding[] = []; const errors: string[] = []; let tokenUsage = 0; // Determine test types to run const testTypes = request.tools || this.getDefaultTests(request.profile); try { // Run OWASP ZAP scan if (testTypes.includes('zap') || testTypes.includes('web_scan')) { const zapResults = await this.runZAP(request.target, testTypes); allFindings.push(...zapResults.findings); tokenUsage += zapResults.tokenUsage; } // Run specific vulnerability tests if (testTypes.includes('sql_injection')) { const sqlResults = await this.testSQLInjection(request.target); allFindings.push(...sqlResults.findings); tokenUsage += sqlResults.tokenUsage; } if (testTypes.includes('xss')) { const xssResults = await this.testXSS(request.target); allFindings.push(...xssResults.findings); tokenUsage += xssResults.tokenUsage; } if (testTypes.includes('security_headers')) { const headerResults = await this.testSecurityHeaders(request.target); allFindings.push(...headerResults.findings); tokenUsage += headerResults.tokenUsage; } } catch (error) { const errorMsg = `Pentest failed: ${error instanceof Error ? error.message : 'Unknown error'}`; console.error(errorMsg); errors.push(errorMsg); } // Calculate summary const summary = this.calculateSummary(allFindings); const result: ScanResult = { scanId, status: errors.length === 0 ? 'success' : (allFindings.length > 0 ? 'partial' : 'failed'), summary, findings: allFindings, tokenUsage, scanTimeMs: Date.now() - startTime, errors: errors.length > 0 ? errors : undefined, }; console.error(`Pentest scan completed: ${result.status}, ${allFindings.length} findings`); return result; } private async runZAP(targetUrl: string, testTypes: string[]): Promise<{ findings: Finding[]; tokenUsage: number }> { const zapConfig = this.getZAPConfig(testTypes); const config: ScannerConfig = { image: 'owasp/zap2docker-stable:latest', command: [ 'zap-baseline.py', '-t', targetUrl, '-J', '/zap/wrk/zap-results.json', '-j', // Use JSON output '-a', // Include all tests ...zapConfig.additionalArgs ], environment: { ZAP_PORT: '8090', }, volumes: [], resourceLimits: { memory: 4 * 1024 * 1024 * 1024, // 4GB cpus: 2, pidsLimit: 300, }, timeout: zapConfig.timeout, networkMode: 'project_network', // Allow access to project apps }; const result = await this.orchestrator.runScanner('zap', config, ''); // ZAP returns different exit codes for different scenarios // 0 = no alerts, 1 = alerts found, 2 = warnings, 3 = errors if (result.exitCode > 3) { throw new Error(`ZAP failed with exit code ${result.exitCode}: ${result.stderr}`); } return this.parseZAPOutput(result.stdout, targetUrl); } private async testSQLInjection(targetUrl: string): Promise<{ findings: Finding[]; tokenUsage: number }> { // Use a lightweight SQL injection test (not full SQLMap for basic tests) const config: ScannerConfig = { image: 'alpine/curl:latest', command: [ 'sh', '-c', ` # Basic SQL injection tests echo "Testing SQL injection endpoints..." # Test common injection points curl -s "${targetUrl}?id=1' OR '1'='1" -o /tmp/sqli_test1.html curl -s "${targetUrl}?search=admin'--" -o /tmp/sqli_test2.html curl -s -X POST "${targetUrl}" -d "username=admin' OR '1'='1'--&password=test" -o /tmp/sqli_test3.html # Check for SQL error messages grep -i "sql\\|mysql\\|error\\|warning" /tmp/sqli_test*.html || echo "No SQL errors detected" ` ], environment: {}, volumes: [], resourceLimits: { memory: 256 * 1024 * 1024, // 256MB cpus: 1, pidsLimit: 10, }, timeout: 60000, // 1 minute }; const result = await this.orchestrator.runScanner('sql-test', config, ''); return this.parseSQLTestOutput(result.stdout, targetUrl); } private async testXSS(targetUrl: string): Promise<{ findings: Finding[]; tokenUsage: number }> { const config: ScannerConfig = { image: 'alpine/curl:latest', command: [ 'sh', '-c', ` # Basic XSS tests echo "Testing XSS vulnerabilities..." # Test reflected XSS curl -s "${targetUrl}?q=<script>alert('XSS')</script>" -o /tmp/xss_test1.html curl -s "${targetUrl}?search=<img src=x onerror=alert('XSS')>" -o /tmp/xss_test2.html # Check if payload is reflected grep -i "script\\|onerror\\|onload" /tmp/xss_test*.html || echo "No XSS reflection detected" ` ], environment: {}, volumes: [], resourceLimits: { memory: 128 * 1024 * 1024, // 128MB cpus: 1, pidsLimit: 10, }, timeout: 60000, // 1 minute }; const result = await this.orchestrator.runScanner('xss-test', config, ''); return this.parseXSSTestOutput(result.stdout, targetUrl); } private async testSecurityHeaders(targetUrl: string): Promise<{ findings: Finding[]; tokenUsage: number }> { const config: ScannerConfig = { image: 'alpine/curl:latest', command: [ 'sh', '-c', ` # Test security headers echo "Testing security headers..." curl -I -s "${targetUrl}" | grep -E "(X-Frame-Options|Content-Security-Policy|X-XSS-Protection|X-Content-Type-Options|Strict-Transport-Security)" || echo "MISSING_SECURITY_HEADERS" ` ], environment: {}, volumes: [], resourceLimits: { memory: 64 * 1024 * 1024, // 64MB cpus: 0.5, pidsLimit: 5, }, timeout: 30000, // 30 seconds }; const result = await this.orchestrator.runScanner('headers-test', config, ''); return this.parseSecurityHeadersOutput(result.stdout, targetUrl); } private getZAPConfig(testTypes: string[]): { additionalArgs: string[]; timeout: number } { const config = { additionalArgs: [] as string[], timeout: 1800000, // 30 minutes default }; if (testTypes.includes('quick') || testTypes.some(t => t === 'quick')) { config.additionalArgs.push('-l', 'Informational'); // Low detail config.timeout = 300000; // 5 minutes } else if (testTypes.includes('thorough')) { config.additionalArgs.push('-l', 'High'); // High detail config.timeout = 3600000; // 1 hour } // Add specific test configurations if (testTypes.includes('api_security')) { config.additionalArgs.push('-z', '-config api.disablekey=true'); } if (testTypes.includes('authentication')) { config.additionalArgs.push('-z', '-config scanner.strength=High'); } return config; } private parseZAPOutput(output: string, targetUrl: string): { findings: Finding[]; tokenUsage: number } { const findings: Finding[] = []; try { // ZAP baseline output includes results in the stdout const lines = output.split('\n'); for (const line of lines) { if (line.includes('WARN') || line.includes('FAIL')) { const severity = line.includes('FAIL') ? 'high' : 'medium'; const description = line.trim(); findings.push({ id: `zap_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`, type: 'web_vulnerability', severity: severity as 'high' | 'medium', title: this.extractZAPTitle(line), description, location: { endpoint: targetUrl, }, remediation: this.getZAPRemediation(line), }); } } } catch (error) { console.error('Failed to parse ZAP output:', error); } return { findings, tokenUsage: Math.min(findings.length * 30 + 100, 500) }; } private parseSQLTestOutput(output: string, targetUrl: string): { findings: Finding[]; tokenUsage: number } { const findings: Finding[] = []; if (output.includes('sql') || output.includes('mysql') || output.includes('error')) { findings.push({ id: `sql_test_${Date.now()}`, type: 'sql_injection', severity: 'high', title: 'Potential SQL Injection Vulnerability', description: 'SQL error messages detected in application response', location: { endpoint: targetUrl, }, remediation: 'Use parameterized queries and input validation', }); } return { findings, tokenUsage: 50 }; } private parseXSSTestOutput(output: string, targetUrl: string): { findings: Finding[]; tokenUsage: number } { const findings: Finding[] = []; if (output.includes('script') || output.includes('onerror') || output.includes('onload')) { findings.push({ id: `xss_test_${Date.now()}`, type: 'cross_site_scripting', severity: 'high', title: 'Potential XSS Vulnerability', description: 'Script tags or event handlers detected in application response', location: { endpoint: targetUrl, }, remediation: 'Implement proper input validation and output encoding', }); } return { findings, tokenUsage: 40 }; } private parseSecurityHeadersOutput(output: string, targetUrl: string): { findings: Finding[]; tokenUsage: number } { const findings: Finding[] = []; if (output.includes('MISSING_SECURITY_HEADERS')) { findings.push({ id: `headers_test_${Date.now()}`, type: 'security_misconfiguration', severity: 'medium', title: 'Missing Security Headers', description: 'Important security headers are missing from the application response', location: { endpoint: targetUrl, }, remediation: 'Add security headers: X-Frame-Options, CSP, X-XSS-Protection, etc.', }); } return { findings, tokenUsage: 30 }; } private extractZAPTitle(line: string): string { // Extract meaningful title from ZAP output line const match = line.match(/\[(.*?)\]/); return match ? match[1] : 'Web Security Issue'; } private getZAPRemediation(line: string): string { if (line.includes('X-Frame-Options')) { return 'Add X-Frame-Options header to prevent clickjacking'; } if (line.includes('Content-Security-Policy')) { return 'Implement Content Security Policy to prevent XSS attacks'; } if (line.includes('HTTPS')) { return 'Enforce HTTPS and implement HSTS'; } return 'Review and fix the identified security issue'; } private calculateSummary(findings: Finding[]) { const summary = { vulnerabilities: findings.length, critical: 0, high: 0, medium: 0, low: 0, informational: 0, }; for (const finding of findings) { summary[finding.severity]++; } return summary; } private getDefaultTests(profile?: string): string[] { switch (profile) { case 'quick': return ['security_headers']; case 'thorough': return ['zap', 'sql_injection', 'xss', 'security_headers']; default: // standard return ['zap', 'security_headers']; } } private generateScanId(): string { return `pentest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } async cleanup(): Promise<void> { await this.orchestrator.cleanup(); } }

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/NeoTecDigital/mcp_shamash'

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