UNPKG

claude-coordination-system

Version:

🤖 Multi-Claude Parallel Processing Coordination System - Organize multiple Claude AI instances to work together seamlessly on complex development tasks

276 lines (235 loc) • 7.6 kB
/** * Enterprise Security Manager * Comprehensive security layer for Claude Coordination System */ const path = require('path'); const crypto = require('crypto'); const { promisify } = require('util'); const chalk = require('chalk'); class SecurityManager { constructor(projectRoot, options = {}) { this.projectRoot = projectRoot; this.options = { maxPathDepth: 10, allowedCommands: ['npx', 'npm', 'tsc', 'eslint', 'prettier'], maxCommandLength: 1000, logSecurityEvents: true, ...options }; this.securityLog = []; } /** * Sanitize and validate file paths to prevent path traversal */ sanitizePath(userPath, allowAbsolute = false) { if (!userPath || typeof userPath !== 'string') { throw new SecurityError('Invalid path input'); } // Remove null bytes and other dangerous characters const cleaned = userPath.replace(/\0/g, '').trim(); if (cleaned.length === 0) { throw new SecurityError('Empty path not allowed'); } // Normalize path to resolve .. and . segments const normalized = path.normalize(cleaned); // Check for path traversal attempts if (normalized.includes('..')) { this.logSecurityIncident('path_traversal_attempt', { originalPath: userPath, normalizedPath: normalized }); throw new SecurityError('Path traversal detected'); } // Resolve to absolute path let resolvedPath; if (path.isAbsolute(normalized)) { if (!allowAbsolute) { throw new SecurityError('Absolute paths not allowed'); } resolvedPath = normalized; } else { resolvedPath = path.resolve(this.projectRoot, normalized); } // Ensure path is within project boundaries if (!resolvedPath.startsWith(this.projectRoot)) { this.logSecurityIncident('path_escape_attempt', { resolvedPath, projectRoot: this.projectRoot }); throw new SecurityError('Path outside project boundaries'); } // Check path depth to prevent deep nesting attacks const relativePath = path.relative(this.projectRoot, resolvedPath); const depth = relativePath.split(path.sep).length; if (depth > this.options.maxPathDepth) { throw new SecurityError(`Path depth exceeds limit: ${depth} > ${this.options.maxPathDepth}`); } return resolvedPath; } /** * Sanitize command arguments to prevent injection */ sanitizeCommand(command, args = []) { if (!command || typeof command !== 'string') { throw new SecurityError('Invalid command'); } const cleanCommand = command.trim(); // Check command whitelist const baseCommand = cleanCommand.split(' ')[0]; if (!this.options.allowedCommands.includes(baseCommand)) { this.logSecurityIncident('unauthorized_command', { command: baseCommand }); throw new SecurityError(`Command not allowed: ${baseCommand}`); } // Validate total command length const fullCommand = [cleanCommand, ...args].join(' '); if (fullCommand.length > this.options.maxCommandLength) { throw new SecurityError('Command length exceeds limit'); } // Check for dangerous patterns const dangerousPatterns = [ /[;&|`$()]/, // Shell metacharacters /\$\{[^}]*\}/, // Variable expansion /\$\([^)]*\)/, // Command substitution /\|\s*\w+/, // Pipes to commands />\s*[\/\w]/, // Output redirection /<\s*[\/\w]/, // Input redirection ]; for (const pattern of dangerousPatterns) { if (pattern.test(fullCommand)) { this.logSecurityIncident('command_injection_attempt', { command: fullCommand, pattern: pattern.source }); throw new SecurityError('Dangerous command pattern detected'); } } return { command: cleanCommand, args: args.map(arg => this.sanitizeCommandArgument(arg)) }; } /** * Sanitize individual command arguments */ sanitizeCommandArgument(arg) { if (typeof arg !== 'string') { return String(arg); } // Remove null bytes and control characters return arg.replace(/[\0-\x1F\x7F]/g, ''); } /** * Validate file access permissions */ async validateFileAccess(filePath, operation = 'read') { const sanitizedPath = this.sanitizePath(filePath); // Check if file is in restricted areas const restrictedPaths = [ '/etc', '/var', '/usr', '/sys', '/proc', path.join(this.projectRoot, 'node_modules'), path.join(this.projectRoot, '.git') ]; for (const restricted of restrictedPaths) { if (sanitizedPath.startsWith(restricted)) { this.logSecurityIncident('restricted_file_access', { path: sanitizedPath, operation }); throw new SecurityError(`Access denied to restricted path: ${restricted}`); } } return sanitizedPath; } /** * Generate secure random tokens */ generateSecureToken(length = 32) { return crypto.randomBytes(length).toString('hex'); } /** * Hash sensitive data */ hashData(data, salt = null) { const actualSalt = salt || crypto.randomBytes(16).toString('hex'); const hash = crypto.pbkdf2Sync(data, actualSalt, 10000, 64, 'sha512'); return { hash: hash.toString('hex'), salt: actualSalt }; } /** * Verify hashed data */ verifyHash(data, hash, salt) { const computed = crypto.pbkdf2Sync(data, salt, 10000, 64, 'sha512'); return computed.toString('hex') === hash; } /** * Log security incidents */ logSecurityIncident(type, details = {}) { if (!this.options.logSecurityEvents) return; const incident = { timestamp: new Date().toISOString(), type, details, severity: this.getIncidentSeverity(type), source: 'SecurityManager' }; this.securityLog.push(incident); // Log to console with appropriate color const color = incident.severity === 'high' ? 'red' : incident.severity === 'medium' ? 'yellow' : 'gray'; console.log(chalk[color](`🚨 SECURITY: ${type} - ${JSON.stringify(details)}`)); // Keep log size manageable if (this.securityLog.length > 1000) { this.securityLog = this.securityLog.slice(-500); } } /** * Get incident severity level */ getIncidentSeverity(type) { const highSeverity = [ 'command_injection_attempt', 'path_traversal_attempt', 'path_escape_attempt', 'unauthorized_command' ]; const mediumSeverity = [ 'restricted_file_access', 'invalid_input' ]; if (highSeverity.includes(type)) return 'high'; if (mediumSeverity.includes(type)) return 'medium'; return 'low'; } /** * Get security log summary */ getSecuritySummary() { const summary = { totalIncidents: this.securityLog.length, highSeverity: this.securityLog.filter(i => i.severity === 'high').length, mediumSeverity: this.securityLog.filter(i => i.severity === 'medium').length, lowSeverity: this.securityLog.filter(i => i.severity === 'low').length, recentIncidents: this.securityLog.slice(-10) }; return summary; } } /** * Custom Security Error class */ class SecurityError extends Error { constructor(message) { super(message); this.name = 'SecurityError'; this.severity = 'high'; } } module.exports = { SecurityManager, SecurityError };