vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
754 lines (753 loc) • 31.3 kB
JavaScript
import { EventEmitter } from 'events';
import { promises as fs } from 'fs';
import { resolve, normalize } from 'path';
import crypto from 'crypto';
import { ErrorFactory, createErrorContext } from '../utils/enhanced-errors.js';
import { createSuccess, createFailure } from './unified-lifecycle-manager.js';
import { getUnifiedSecurityConfig } from '../security/unified-security-config.js';
import logger from '../../../logger.js';
export function createSecurityId(id) {
if (!id || id.trim().length === 0) {
throw new Error('Security ID cannot be empty');
}
return id;
}
export function createSessionId(id) {
if (!id || id.trim().length === 0) {
throw new Error('Session ID cannot be empty');
}
return id;
}
export function createLockId(id) {
if (!id || id.trim().length === 0) {
throw new Error('Lock ID cannot be empty');
}
return id;
}
export function createAuditId(id) {
if (!id || id.trim().length === 0) {
throw new Error('Audit ID cannot be empty');
}
return id;
}
export class UnifiedSecurityEngine extends EventEmitter {
static instance = null;
config;
initialized = false;
activeSessions = new Map();
activeLocks = new Map();
auditEvents = new Map();
permissionCache = new Map();
eventCount = 0;
eventsByType = new Map();
eventsBySeverity = new Map();
violationCount = 0;
blockedAttempts = 0;
totalResponseTime = 0;
lockCleanupTimer = null;
auditCleanupTimer = null;
metricsTimer = null;
constructor(config) {
super();
this.config = config;
['authentication', 'authorization', 'access_attempt', 'data_access', 'data_modification', 'security_violation', 'system_event', 'suspicious_activity', 'compliance_event', 'error_event'].forEach(type => {
this.eventsByType.set(type, 0);
});
['info', 'low', 'medium', 'high', 'critical'].forEach(severity => {
this.eventsBySeverity.set(severity, 0);
});
logger.info('Unified Security Engine initialized');
}
static getInstance(config) {
if (!UnifiedSecurityEngine.instance) {
if (!config) {
throw new Error('Configuration required for first initialization');
}
UnifiedSecurityEngine.instance = new UnifiedSecurityEngine(config);
}
return UnifiedSecurityEngine.instance;
}
static resetInstance() {
if (UnifiedSecurityEngine.instance) {
UnifiedSecurityEngine.instance.dispose();
UnifiedSecurityEngine.instance = null;
}
}
async initialize() {
if (this.initialized) {
return createSuccess(undefined);
}
try {
await this.loadSecurityConfiguration();
await this.initializeSecurityComponents();
this.startBackgroundProcesses();
this.initialized = true;
this.emit('initialized');
logger.info('Security engine initialized successfully');
return createSuccess(undefined);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Failed to initialize security engine: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'initialize').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async loadSecurityConfiguration() {
try {
getUnifiedSecurityConfig();
logger.info('Security configuration loaded successfully');
}
catch (error) {
logger.warn('Failed to load security configuration, using defaults:', error);
}
}
async initializeSecurityComponents() {
await this.initializeAuditLogging();
await this.initializePathSecurity();
await this.initializeConcurrentAccess();
logger.info('Security components initialized');
}
async initializeAuditLogging() {
if (!this.config.audit.enabled) {
return;
}
await this.logSecurityEvent({
type: 'system_event',
severity: 'info',
action: 'security_engine_startup',
result: 'success',
details: {
config: {
strictMode: this.config.strictMode,
auditEnabled: this.config.audit.enabled,
pathSecurityEnabled: this.config.pathSecurity.enabled
}
}
});
}
async initializePathSecurity() {
if (!this.config.pathSecurity.enabled) {
return;
}
for (const path of this.config.pathSecurity.allowedReadPaths) {
try {
await fs.access(path);
}
catch {
logger.warn(`Configured read path does not exist: ${path}`);
}
}
for (const path of this.config.pathSecurity.allowedWritePaths) {
try {
await fs.access(path);
}
catch {
logger.warn(`Configured write path does not exist: ${path}`);
}
}
}
async initializeConcurrentAccess() {
if (!this.config.concurrentAccess.enabled) {
return;
}
await this.cleanupStaleLocks();
}
startBackgroundProcesses() {
if (this.config.concurrentAccess.enabled) {
this.lockCleanupTimer = setInterval(() => {
this.cleanupStaleLocks().catch(error => {
logger.error('Lock cleanup process failed:', error);
});
}, this.config.concurrentAccess.lockCleanupInterval * 1000);
}
if (this.config.audit.enabled) {
this.auditCleanupTimer = setInterval(() => {
this.cleanupOldAuditEvents().catch(error => {
logger.error('Audit cleanup process failed:', error);
});
}, 24 * 60 * 60 * 1000);
}
this.metricsTimer = setInterval(() => {
this.collectMetrics();
}, 60 * 1000);
logger.info('Security background processes started');
}
async authenticateUser(credentials) {
const startTime = Date.now();
try {
if (!this.config.authentication.enabled) {
return createFailure(ErrorFactory.createError('permission', 'Authentication is disabled', createErrorContext('UnifiedSecurityEngine', 'authenticateUser').build()));
}
const isValid = await this.validateCredentials(credentials);
if (!isValid) {
await this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
action: 'authentication_failed',
result: 'failure',
details: { userId: credentials.userId, reason: 'invalid_credentials' }
});
return createFailure(ErrorFactory.createError('permission', 'Invalid credentials', createErrorContext('UnifiedSecurityEngine', 'authenticateUser').build()));
}
const sessionId = createSessionId(crypto.randomUUID());
const userContext = {
userId: credentials.userId,
sessionId,
role: this.determineUserRole(credentials.userId),
permissions: await this.getUserPermissions(credentials.userId),
authenticatedAt: new Date(),
lastActivity: new Date(),
metadata: credentials.metadata || {}
};
this.activeSessions.set(sessionId, userContext);
await this.logSecurityEvent({
type: 'authentication',
severity: 'info',
action: 'authentication_success',
result: 'success',
details: { userId: credentials.userId, sessionId, role: userContext.role }
});
this.trackPerformance('authentication', Date.now() - startTime);
this.emit('userAuthenticated', userContext);
return createSuccess(userContext);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'authenticateUser').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async authorizeAction(sessionId, permission, resource) {
const startTime = Date.now();
try {
if (!this.config.authorization.enabled) {
return createSuccess(true);
}
const userContext = this.activeSessions.get(sessionId);
if (!userContext) {
await this.logSecurityEvent({
type: 'authorization',
severity: 'medium',
action: 'authorization_failed',
result: 'failure',
details: { sessionId, permission, resource, reason: 'invalid_session' }
});
return createFailure(ErrorFactory.createError('permission', 'Invalid session', createErrorContext('UnifiedSecurityEngine', 'authorizeAction').build()));
}
const hasPermission = userContext.permissions.includes(permission) ||
userContext.permissions.includes('system:admin');
if (!hasPermission) {
await this.logSecurityEvent({
type: 'authorization',
severity: 'medium',
action: 'authorization_denied',
result: 'blocked',
details: {
userId: userContext.userId,
sessionId,
permission,
resource,
userRole: userContext.role,
userPermissions: userContext.permissions
}
});
this.blockedAttempts++;
return createSuccess(false);
}
userContext.lastActivity = new Date();
await this.logSecurityEvent({
type: 'authorization',
severity: 'info',
action: 'authorization_granted',
result: 'success',
details: { userId: userContext.userId, sessionId, permission, resource }
});
this.trackPerformance('authorization', Date.now() - startTime);
return createSuccess(true);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Authorization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'authorizeAction').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async validatePath(inputPath, operation) {
const startTime = Date.now();
try {
if (!this.config.pathSecurity.enabled) {
return createSuccess({
isValid: true,
normalizedPath: inputPath,
auditInfo: {
originalPath: inputPath,
timestamp: new Date(),
validationTime: Date.now() - startTime
}
});
}
const result = {
isValid: false,
auditInfo: {
originalPath: inputPath,
timestamp: new Date(),
validationTime: 0
}
};
let normalizedPath;
try {
normalizedPath = resolve(normalize(inputPath));
}
catch {
result.error = 'Invalid path format';
result.violationType = 'invalid_path';
result.auditInfo.validationTime = Date.now() - startTime;
return createSuccess(result);
}
if (inputPath.includes('..') || inputPath.includes('~')) {
result.error = 'Path traversal detected';
result.violationType = 'path_traversal';
result.auditInfo.validationTime = Date.now() - startTime;
await this.logSecurityEvent({
type: 'security_violation',
severity: 'high',
action: 'path_traversal_attempt',
result: 'blocked',
details: { originalPath: inputPath, normalizedPath, operation }
});
this.violationCount++;
return createSuccess(result);
}
const allowedPaths = operation === 'read'
? this.config.pathSecurity.allowedReadPaths
: this.config.pathSecurity.allowedWritePaths;
const isAllowed = allowedPaths.some(allowedPath => {
const resolvedAllowedPath = resolve(allowedPath);
return normalizedPath.startsWith(resolvedAllowedPath);
});
if (!isAllowed) {
result.error = 'Path outside allowed boundaries';
result.violationType = 'outside_boundary';
result.auditInfo.validationTime = Date.now() - startTime;
await this.logSecurityEvent({
type: 'security_violation',
severity: 'medium',
action: 'boundary_violation',
result: 'blocked',
details: { originalPath: inputPath, normalizedPath, operation, allowedPaths }
});
this.violationCount++;
return createSuccess(result);
}
if (this.config.pathSecurity.allowedExtensions.length > 0) {
const ext = inputPath.split('.').pop()?.toLowerCase();
const extWithDot = ext ? `.${ext}` : '';
if (ext && !this.config.pathSecurity.allowedExtensions.includes(extWithDot)) {
result.error = 'File extension not allowed';
result.violationType = 'invalid_extension';
result.warnings = [`Extension '${extWithDot}' is not in allowed list: ${this.config.pathSecurity.allowedExtensions.join(', ')}`];
result.auditInfo.validationTime = Date.now() - startTime;
return createSuccess(result);
}
}
result.isValid = true;
result.normalizedPath = normalizedPath;
result.auditInfo.validationTime = Date.now() - startTime;
this.trackPerformance('path_validation', Date.now() - startTime);
return createSuccess(result);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Path validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'validatePath').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async sanitizeData(data) {
const startTime = Date.now();
try {
if (!this.config.dataSanitization.enabled) {
return createSuccess({
success: true,
sanitizedData: data,
originalData: data,
violations: [],
sanitizationTime: Date.now() - startTime
});
}
const result = {
success: false,
originalData: data,
violations: [],
sanitizationTime: 0
};
let sanitizedData;
if (typeof data === 'string') {
sanitizedData = this.sanitizeString(data);
}
else if (typeof data === 'object' && data !== null) {
sanitizedData = this.sanitizeObject(data);
}
else {
sanitizedData = data;
}
result.success = true;
result.sanitizedData = sanitizedData;
result.sanitizationTime = Date.now() - startTime;
this.trackPerformance('sanitization', Date.now() - startTime);
return createSuccess(result);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Data sanitization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'sanitizeData').build(), { cause: error instanceof Error ? error : undefined }));
}
}
sanitizeString(input) {
let sanitized = input;
if (!this.config.dataSanitization.allowScripts) {
sanitized = sanitized.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}
if (!this.config.dataSanitization.allowHtml) {
sanitized = sanitized.replace(/<[^>]*>/g, '');
}
if (sanitized.length > this.config.dataSanitization.maxStringLength) {
sanitized = sanitized.substring(0, this.config.dataSanitization.maxStringLength);
}
sanitized = sanitized.replace(/\0/g, '');
return sanitized;
}
sanitizeObject(input) {
if (Array.isArray(input)) {
return input.map(item => this.sanitizeObject(item));
}
if (typeof input === 'object' && input !== null) {
const sanitized = {};
for (const [key, value] of Object.entries(input)) {
const sanitizedKey = this.sanitizeString(key);
sanitized[sanitizedKey] = this.sanitizeObject(value);
}
return sanitized;
}
if (typeof input === 'string') {
return this.sanitizeString(input);
}
return input;
}
async acquireLock(resource, operation, sessionId, timeoutMs) {
const startTime = Date.now();
try {
if (!this.config.concurrentAccess.enabled) {
return createSuccess({
success: true,
lockId: createLockId(crypto.randomUUID())
});
}
const lockId = createLockId(crypto.randomUUID());
const timeout = timeoutMs || this.config.concurrentAccess.maxLockDuration;
const existingLock = Array.from(this.activeLocks.values()).find(lock => lock.resource === resource &&
(lock.operation === 'write' || operation === 'write'));
if (existingLock) {
return createSuccess({
success: false,
error: 'Resource is locked',
existingLock,
waitTime: Date.now() - startTime
});
}
const lockInfo = {
id: lockId,
resource,
owner: sessionId ? this.activeSessions.get(sessionId)?.userId || 'unknown' : 'system',
sessionId,
operation,
acquiredAt: new Date(),
expiresAt: new Date(Date.now() + timeout),
metadata: {}
};
this.activeLocks.set(lockId, lockInfo);
await this.logSecurityEvent({
type: 'system_event',
severity: 'info',
action: 'lock_acquired',
result: 'success',
details: { lockId, resource, operation, owner: lockInfo.owner }
});
this.emit('lockAcquired', lockInfo);
return createSuccess({
success: true,
lockId,
waitTime: Date.now() - startTime
});
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Lock acquisition failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'acquireLock').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async releaseLock(lockId) {
try {
const lockInfo = this.activeLocks.get(lockId);
if (!lockInfo) {
return createFailure(ErrorFactory.createError('validation', `Lock not found: ${lockId}`, createErrorContext('UnifiedSecurityEngine', 'releaseLock').build()));
}
this.activeLocks.delete(lockId);
await this.logSecurityEvent({
type: 'system_event',
severity: 'info',
action: 'lock_released',
result: 'success',
details: { lockId, resource: lockInfo.resource, owner: lockInfo.owner }
});
this.emit('lockReleased', lockInfo);
return createSuccess(undefined);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Lock release failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'releaseLock').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async logSecurityEvent(event) {
try {
if (!this.config.audit.enabled) {
return createSuccess(createAuditId('disabled'));
}
const auditId = createAuditId(crypto.randomUUID());
const auditEvent = {
id: auditId,
timestamp: new Date(),
metadata: {},
...event
};
this.auditEvents.set(auditId, auditEvent);
this.eventCount++;
this.eventsByType.set(event.type, (this.eventsByType.get(event.type) || 0) + 1);
this.eventsBySeverity.set(event.severity, (this.eventsBySeverity.get(event.severity) || 0) + 1);
const logMessage = `Security Event: ${event.action} - ${event.result}`;
switch (event.severity) {
case 'critical':
case 'high':
logger.error(logMessage, auditEvent);
break;
case 'medium':
logger.warn(logMessage, auditEvent);
break;
default:
logger.info(logMessage, auditEvent);
}
this.emit('securityEvent', auditEvent);
return createSuccess(auditId);
}
catch (error) {
return createFailure(ErrorFactory.createError('system', `Security event logging failed: ${error instanceof Error ? error.message : 'Unknown error'}`, createErrorContext('UnifiedSecurityEngine', 'logSecurityEvent').build(), { cause: error instanceof Error ? error : undefined }));
}
}
async validateCredentials(credentials) {
return credentials.userId.length > 0 && credentials.token.length > 0;
}
determineUserRole(userId) {
if (userId === 'admin')
return 'admin';
if (userId.startsWith('manager'))
return 'manager';
if (userId.startsWith('dev'))
return 'developer';
return 'viewer';
}
async getUserPermissions(userId) {
const cacheKey = `permissions:${userId}`;
if (this.config.authorization.permissionCache && this.permissionCache.has(cacheKey)) {
return this.permissionCache.get(cacheKey);
}
const role = this.determineUserRole(userId);
let permissions = [];
switch (role) {
case 'admin':
permissions = ['system:admin', 'system:config', 'system:audit'];
break;
case 'manager':
permissions = ['project:create', 'project:read', 'project:update', 'task:create', 'task:read', 'task:update', 'agent:manage'];
break;
case 'developer':
permissions = ['task:create', 'task:read', 'task:update', 'task:execute', 'file:read', 'file:write'];
break;
case 'viewer':
permissions = ['task:read', 'project:read', 'file:read'];
break;
default:
permissions = [];
}
if (this.config.authorization.permissionCache) {
this.permissionCache.set(cacheKey, permissions);
}
return permissions;
}
async cleanupStaleLocks() {
const now = new Date();
const staleLocks = [];
for (const [lockId, lockInfo] of this.activeLocks.entries()) {
if (lockInfo.expiresAt < now) {
staleLocks.push(lockId);
}
}
for (const lockId of staleLocks) {
const lockInfo = this.activeLocks.get(lockId);
this.activeLocks.delete(lockId);
if (lockInfo) {
await this.logSecurityEvent({
type: 'system_event',
severity: 'info',
action: 'lock_expired',
result: 'success',
details: { lockId, resource: lockInfo.resource, owner: lockInfo.owner }
});
}
}
if (staleLocks.length > 0) {
logger.debug(`Cleaned up ${staleLocks.length} stale locks`);
}
}
async cleanupOldAuditEvents() {
const cutoffDate = new Date(Date.now() - this.config.audit.retentionDays * 24 * 60 * 60 * 1000);
const oldEvents = [];
for (const [auditId, event] of this.auditEvents.entries()) {
if (event.timestamp < cutoffDate) {
oldEvents.push(auditId);
}
}
for (const auditId of oldEvents) {
this.auditEvents.delete(auditId);
}
if (oldEvents.length > 0) {
logger.debug(`Cleaned up ${oldEvents.length} old audit events`);
}
}
trackPerformance(operation, responseTime) {
this.totalResponseTime += responseTime;
if (responseTime > this.config.performanceThresholdMs) {
logger.warn(`Security operation '${operation}' exceeded performance threshold: ${responseTime}ms`);
}
}
collectMetrics() {
const stats = {
totalEvents: this.eventCount,
eventsByType: Object.fromEntries(this.eventsByType),
eventsBySeverity: Object.fromEntries(this.eventsBySeverity),
violationCount: this.violationCount,
blockedAttempts: this.blockedAttempts,
activeUsers: new Set(Array.from(this.activeSessions.values()).map(s => s.userId)).size,
activeSessions: this.activeSessions.size,
activeLocks: this.activeLocks.size,
averageResponseTime: this.eventCount > 0 ? this.totalResponseTime / this.eventCount : 0,
securityScore: this.calculateSecurityScore()
};
this.emit('metricsCollected', stats);
}
calculateSecurityScore() {
let score = 100;
if (this.violationCount > 0) {
score -= Math.min(this.violationCount * 2, 30);
}
if (this.blockedAttempts > 0) {
score -= Math.min(this.blockedAttempts * 1, 20);
}
const avgResponseTime = this.eventCount > 0 ? this.totalResponseTime / this.eventCount : 0;
if (avgResponseTime > this.config.performanceThresholdMs) {
score -= 10;
}
return Math.max(score, 0);
}
getStatistics() {
return {
totalEvents: this.eventCount,
eventsByType: Object.fromEntries(this.eventsByType),
eventsBySeverity: Object.fromEntries(this.eventsBySeverity),
violationCount: this.violationCount,
blockedAttempts: this.blockedAttempts,
activeUsers: new Set(Array.from(this.activeSessions.values()).map(s => s.userId)).size,
activeSessions: this.activeSessions.size,
activeLocks: this.activeLocks.size,
averageResponseTime: this.eventCount > 0 ? this.totalResponseTime / this.eventCount : 0,
securityScore: this.calculateSecurityScore()
};
}
dispose() {
if (this.lockCleanupTimer) {
clearInterval(this.lockCleanupTimer);
this.lockCleanupTimer = null;
}
if (this.auditCleanupTimer) {
clearInterval(this.auditCleanupTimer);
this.auditCleanupTimer = null;
}
if (this.metricsTimer) {
clearInterval(this.metricsTimer);
this.metricsTimer = null;
}
this.activeSessions.clear();
this.activeLocks.clear();
this.auditEvents.clear();
this.permissionCache.clear();
this.removeAllListeners();
this.initialized = false;
logger.info('Unified Security Engine disposed');
}
}
export function createDefaultSecurityConfig() {
return {
enabled: true,
strictMode: false,
performanceThresholdMs: 1000,
logViolations: true,
blockOnCriticalViolations: true,
authentication: {
enabled: true,
tokenExpiryMinutes: 60,
maxSessionsPerUser: 5,
requireStrongPasswords: true,
enableMFA: false
},
authorization: {
enabled: true,
defaultRole: 'viewer',
roleHierarchy: {
admin: ['manager', 'developer', 'viewer', 'guest'],
manager: ['developer', 'viewer', 'guest'],
developer: ['viewer', 'guest'],
viewer: ['guest'],
guest: []
},
permissionCache: true
},
pathSecurity: {
enabled: true,
allowedReadPaths: [process.cwd()],
allowedWritePaths: [process.cwd()],
allowedExtensions: ['.ts', '.js', '.json', '.md', '.txt', '.yaml', '.yml'],
blockSystemPaths: true,
followSymlinks: false
},
dataSanitization: {
enabled: true,
strictMode: false,
allowHtml: false,
allowScripts: false,
maxStringLength: 10000,
sanitizeFileNames: true
},
concurrentAccess: {
enabled: true,
maxLockDuration: 300000,
deadlockDetection: true,
lockCleanupInterval: 60,
maxLocksPerResource: 10
},
audit: {
enabled: true,
logLevel: 'info',
retentionDays: 30,
enableIntegrityChecks: true,
compressLogs: false
},
filesystem: {
enabled: true,
systemDirectoryBlacklist: [
'/private/var/spool',
'/System',
'/usr/bin',
'/usr/sbin',
'/bin',
'/sbin'
],
maxFileSize: 10485760,
allowedMimeTypes: ['text/plain', 'application/json', 'text/markdown'],
scanForMalware: false
}
};
}