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}`);
}
}