Skip to main content
Glama

MCP Pentest

validation.ts•11.1 kB
import { URL } from 'url'; import { ConfigManager } from '../config/settings.js'; export class ValidationError extends Error { constructor(message: string, public code: string) { super(message); this.name = 'ValidationError'; } } export class TargetValidator { private configManager: ConfigManager; constructor(configManager: ConfigManager) { this.configManager = configManager; } validateTarget(target: string): { isValid: boolean; error?: string; type: 'ip' | 'domain' | 'url' } { try { // Check if it's a URL if (this.isURL(target)) { const url = new URL(target); const hostname = url.hostname; if (this.isIP(hostname)) { return this.validateIP(hostname); } else { return this.validateDomain(hostname); } } // Check if it's an IP address if (this.isIP(target)) { return this.validateIP(target); } // Check if it's a domain if (this.isDomain(target)) { return this.validateDomain(target); } return { isValid: false, error: 'Invalid target format. Must be IP address, domain, or URL', type: 'domain' }; } catch (error) { return { isValid: false, error: `Target validation failed: ${error instanceof Error ? error.message : String(error)}`, type: 'domain' }; } } private validateIP(ip: string): { isValid: boolean; error?: string; type: 'ip' } { // Check if IP is in allowed range if (!this.configManager.isTargetAllowed(ip)) { return { isValid: false, error: 'Target IP is in a blocked network range', type: 'ip' }; } // Check for localhost/loopback if (ip.startsWith('127.') || ip === 'localhost') { return { isValid: false, error: 'Localhost/loopback addresses are not allowed', type: 'ip' }; } // Check for private IP ranges (additional security) if (this.isPrivateIP(ip)) { return { isValid: false, error: 'Private IP addresses require special authorization', type: 'ip' }; } return { isValid: true, type: 'ip' }; } private validateDomain(domain: string): { isValid: boolean; error?: string; type: 'domain' } { // Check for localhost variations if (domain === 'localhost' || domain.endsWith('.localhost') || domain.endsWith('.local')) { return { isValid: false, error: 'Localhost domains are not allowed', type: 'domain' }; } // Check for internal domains if (domain.endsWith('.internal') || domain.endsWith('.corp') || domain.endsWith('.test')) { return { isValid: false, error: 'Internal/test domains require special authorization', type: 'domain' }; } // Basic domain format validation if (!this.isDomainValid(domain)) { return { isValid: false, error: 'Invalid domain format', type: 'domain' }; } return { isValid: true, type: 'domain' }; } private isURL(target: string): boolean { try { new URL(target); return true; } catch { return false; } } private isIP(target: string): boolean { const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return ipRegex.test(target); } private isDomain(target: string): boolean { // Basic domain validation const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/; return domainRegex.test(target); } private isDomainValid(domain: string): boolean { // More comprehensive domain validation if (domain.length > 253) return false; if (domain.startsWith('-') || domain.endsWith('-')) return false; if (domain.includes('..')) return false; const parts = domain.split('.'); if (parts.length < 2) return false; for (const part of parts) { if (part.length === 0 || part.length > 63) return false; if (part.startsWith('-') || part.endsWith('-')) return false; if (!/^[a-zA-Z0-9-]+$/.test(part)) return false; } return true; } private isPrivateIP(ip: string): boolean { const octets = ip.split('.').map(Number); // 10.0.0.0/8 if (octets[0] === 10) return true; // 172.16.0.0/12 if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) return true; // 192.168.0.0/16 if (octets[0] === 192 && octets[1] === 168) return true; return false; } } export class InputSanitizer { static sanitizeString(input: string): string { // Remove potentially dangerous characters return input.replace(/[;&|`$<>]/g, ''); } static sanitizeFilename(filename: string): string { // Remove path traversal and dangerous characters return filename .replace(/[\/\\:*?"<>|]/g, '') .replace(/\.\./g, '') .replace(/^\.|\.$/g, '') .substring(0, 255); } static sanitizeURL(url: string): string { try { const parsed = new URL(url); // Only allow http and https protocols if (!['http:', 'https:'].includes(parsed.protocol)) { throw new ValidationError('Only HTTP and HTTPS protocols are allowed', 'INVALID_PROTOCOL'); } return parsed.toString(); } catch (error) { throw new ValidationError('Invalid URL format', 'INVALID_URL'); } } static sanitizePort(port: string | number): number { const portNum = typeof port === 'string' ? parseInt(port, 10) : port; if (isNaN(portNum) || portNum < 1 || portNum > 65535) { throw new ValidationError('Port must be between 1 and 65535', 'INVALID_PORT'); } return portNum; } static sanitizeIPAddress(ip: string): string { // Basic IP address validation and sanitization const sanitized = ip.trim(); if (!sanitized.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)) { throw new ValidationError('Invalid IP address format', 'INVALID_IP'); } return sanitized; } } export class RateLimiter { private requests: Map<string, number[]> = new Map(); private configManager: ConfigManager; constructor(configManager: ConfigManager) { this.configManager = configManager; } checkRateLimit(identifier: string): boolean { const now = Date.now(); const rateConfig = this.configManager.getRateLimit(); const windowMs = 1000; // 1 second window if (!this.requests.has(identifier)) { this.requests.set(identifier, []); } const requests = this.requests.get(identifier)!; // Remove old requests outside the window const validRequests = requests.filter(timestamp => now - timestamp < windowMs); // Check if we're within rate limits if (validRequests.length >= rateConfig.requestsPerSecond) { return false; } // Add current request validRequests.push(now); this.requests.set(identifier, validRequests); return true; } reset(identifier?: string): void { if (identifier) { this.requests.delete(identifier); } else { this.requests.clear(); } } } export class SecurityValidator { private configManager: ConfigManager; private targetValidator: TargetValidator; private rateLimiter: RateLimiter; constructor(configManager: ConfigManager) { this.configManager = configManager; this.targetValidator = new TargetValidator(configManager); this.rateLimiter = new RateLimiter(configManager); } validatePentestRequest(target: string, clientId: string = 'default'): void { // Rate limiting check if (!this.rateLimiter.checkRateLimit(clientId)) { throw new ValidationError('Rate limit exceeded', 'RATE_LIMIT_EXCEEDED'); } // Target validation const validation = this.targetValidator.validateTarget(target); if (!validation.isValid) { throw new ValidationError(validation.error || 'Invalid target', 'INVALID_TARGET'); } // Authorization check (if required) if (this.configManager.getConfig().security.requireAuthorization) { // In a real implementation, this would check against an authorization database console.warn('Authorization check required but not implemented'); } } validateToolExecution(toolName: string, args: any): void { // Validate tool name const allowedTools = [ 'nmap_scan', 'subdomain_enum', 'tech_detection', 'directory_bruteforce', 'nuclei_scan', 'nikto_scan', 'sqlmap_scan', 'metasploit_search', 'exploit_attempt', 'auto_pentest', 'suggest_next_steps', 'generate_report' ]; if (!allowedTools.includes(toolName)) { throw new ValidationError(`Tool '${toolName}' is not allowed`, 'INVALID_TOOL'); } // Validate arguments based on tool switch (toolName) { case 'nmap_scan': this.validateNmapArgs(args); break; case 'nuclei_scan': this.validateNucleiArgs(args); break; case 'exploit_attempt': this.validateExploitArgs(args); break; // Add more tool-specific validations as needed } } private validateNmapArgs(args: any): void { if (args.target) { const validation = this.targetValidator.validateTarget(args.target); if (!validation.isValid) { throw new ValidationError(`Invalid nmap target: ${validation.error}`, 'INVALID_NMAP_TARGET'); } } const allowedScanTypes = ['quick', 'full', 'stealth', 'aggressive']; if (args.scan_type && !allowedScanTypes.includes(args.scan_type)) { throw new ValidationError('Invalid scan type', 'INVALID_SCAN_TYPE'); } } private validateNucleiArgs(args: any): void { if (args.target) { const validation = this.targetValidator.validateTarget(args.target); if (!validation.isValid) { throw new ValidationError(`Invalid nuclei target: ${validation.error}`, 'INVALID_NUCLEI_TARGET'); } } const allowedSeverities = ['info', 'low', 'medium', 'high', 'critical']; if (args.severity && !allowedSeverities.includes(args.severity)) { throw new ValidationError('Invalid severity level', 'INVALID_SEVERITY'); } } private validateExploitArgs(args: any): void { if (args.target) { const validation = this.targetValidator.validateTarget(args.target); if (!validation.isValid) { throw new ValidationError(`Invalid exploit target: ${validation.error}`, 'INVALID_EXPLOIT_TARGET'); } } // Additional checks for exploitation attempts if (!args.vulnerability) { throw new ValidationError('Vulnerability identifier required for exploitation', 'MISSING_VULNERABILITY'); } // Log exploitation attempts for audit purposes console.log(`AUDIT: Exploitation attempt - Target: ${args.target}, Vulnerability: ${args.vulnerability}`); } }

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/adriyansyah-mf/mcp-pentest'

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