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
JavaScript
/**
* 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 };