/**
* Security Module for MCP Interface Validator
*
* Provides comprehensive security features including:
* - Authentication and authorization
* - Data encryption and decryption
* - Security logging and audit trails
* - Input validation and sanitization
* - Rate limiting and access control
*/
import { EventEmitter } from 'events';
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from 'crypto';
import { Logger } from '../../utils/logger/index.js';
/**
* Main Security Manager Class
*/
export class SecurityManager extends EventEmitter {
config;
logger;
users = new Map();
sessions = new Map();
securityEvents = [];
rateLimitStore = new Map();
cleanupIntervals = [];
isInitialized = false;
constructor(config, logger) {
super();
this.config = this.validateAndMergeConfig(config);
this.logger = logger;
}
/**
* Initialize the security manager
*/
async initialize() {
try {
this.logger.info('Initializing Security Manager...');
// Load users and sessions from storage (in real implementation)
await this.loadUsersFromStorage();
await this.loadSessionsFromStorage();
// Set up cleanup intervals
this.setupCleanupIntervals();
// Create default admin user if none exists
await this.createDefaultAdminUser();
this.isInitialized = true;
this.logger.info('Security Manager initialized successfully');
this.logSecurityEvent({
type: 'access',
severity: 'low',
message: 'Security Manager initialized'
});
}
catch (error) {
this.logger.error('Failed to initialize Security Manager:', error);
throw error;
}
}
/**
* Authenticate user with username/password
*/
async authenticate(username, password, ipAddress, userAgent) {
if (!this.isInitialized) {
throw new Error('Security Manager not initialized');
}
try {
// Check rate limiting
if (this.config.rateLimit.enabled && !this.checkRateLimit(ipAddress || 'unknown')) {
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: 'Authentication rate limit exceeded',
ipAddress,
userAgent
});
return { success: false, error: 'Rate limit exceeded' };
}
// Find user
const user = Array.from(this.users.values()).find(u => u.username === username);
if (!user) {
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: `Authentication failed: user not found - ${username}`,
ipAddress,
userAgent
});
return { success: false, error: 'Invalid credentials' };
}
// Check if user is locked
if (user.lockedUntil && user.lockedUntil > new Date()) {
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: `Authentication failed: user locked - ${username}`,
userId: user.id,
ipAddress,
userAgent
});
return { success: false, error: 'Account locked' };
}
// Check if user is active
if (!user.isActive) {
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: `Authentication failed: user inactive - ${username}`,
userId: user.id,
ipAddress,
userAgent
});
return { success: false, error: 'Account inactive' };
}
// Verify password
const passwordHash = this.hashPassword(password, user.salt);
if (passwordHash !== user.passwordHash) {
// Increment failed login attempts
user.failedLoginAttempts++;
if (user.failedLoginAttempts >= this.config.authentication.maxLoginAttempts) {
user.lockedUntil = new Date(Date.now() + this.config.authentication.lockoutDuration * 1000);
}
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: `Authentication failed: invalid password - ${username}`,
userId: user.id,
ipAddress,
userAgent
});
return { success: false, error: 'Invalid credentials' };
}
// Reset failed login attempts
user.failedLoginAttempts = 0;
user.lockedUntil = undefined;
user.lastLogin = new Date();
// Check if two-factor authentication is required
if (this.config.authentication.requireTwoFactor && user.twoFactorSecret) {
this.logSecurityEvent({
type: 'authentication',
severity: 'low',
message: `Two-factor authentication required - ${username}`,
userId: user.id,
ipAddress,
userAgent
});
return { success: true, user, requiresTwoFactor: true };
}
// Create session
const session = await this.createSession(user.id, ipAddress, userAgent);
this.logSecurityEvent({
type: 'authentication',
severity: 'low',
message: `Authentication successful - ${username}`,
userId: user.id,
sessionId: session.id,
ipAddress,
userAgent
});
return { success: true, user, session, token: session.token };
}
catch (error) {
this.logger.error('Authentication error:', error);
this.logSecurityEvent({
type: 'error',
severity: 'high',
message: `Authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`,
ipAddress,
userAgent
});
return { success: false, error: 'Authentication failed' };
}
}
/**
* Validate session token
*/
async validateSession(token) {
if (!this.isInitialized) {
throw new Error('Security Manager not initialized');
}
const session = Array.from(this.sessions.values()).find(s => s.token === token);
if (!session || !session.isValid || session.expiresAt < new Date()) {
if (session) {
this.logSecurityEvent({
type: 'authentication',
severity: 'low',
message: 'Session validation failed: expired or invalid',
sessionId: session.id,
userId: session.userId
});
}
return null;
}
// Update last activity
session.lastActivity = new Date();
return session;
}
/**
* Check if user has permission for a specific action
*/
async authorize(userId, resource, action, context) {
if (!this.isInitialized) {
throw new Error('Security Manager not initialized');
}
if (!this.config.authorization.enabled) {
return { allowed: true };
}
try {
const user = this.users.get(userId);
if (!user) {
this.logSecurityEvent({
type: 'authorization',
severity: 'medium',
message: `Authorization failed: user not found`,
userId
});
return { allowed: false, reason: 'User not found' };
}
// Check user permissions through roles
const hasPermission = await this.checkUserPermission(user, resource, action, context);
if (!hasPermission) {
this.logSecurityEvent({
type: 'authorization',
severity: 'medium',
message: `Authorization denied: ${resource}:${action}`,
userId,
metadata: { resource, action, context }
});
return {
allowed: false,
reason: 'Insufficient permissions',
requiredPermissions: [{ resource, action }]
};
}
this.logSecurityEvent({
type: 'authorization',
severity: 'low',
message: `Authorization granted: ${resource}:${action}`,
userId,
metadata: { resource, action, context }
});
return { allowed: true };
}
catch (error) {
this.logger.error('Authorization error:', error);
this.logSecurityEvent({
type: 'error',
severity: 'high',
message: `Authorization error: ${error instanceof Error ? error.message : 'Unknown error'}`,
userId,
metadata: { resource, action, context }
});
return { allowed: false, reason: 'Authorization error' };
}
}
/**
* Encrypt sensitive data
*/
encrypt(data, password) {
try {
const salt = randomBytes(this.config.encryption.saltLength);
const iv = randomBytes(this.config.encryption.ivLength);
// Derive key from password or use default
const key = password
? pbkdf2Sync(password, salt, this.config.encryption.iterations, 32, 'sha256')
: pbkdf2Sync('default-key', salt, this.config.encryption.iterations, 32, 'sha256');
const cipher = createCipheriv(this.config.encryption.algorithm, key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag ? cipher.getAuthTag().toString('hex') : undefined;
this.logSecurityEvent({
type: 'encryption',
severity: 'low',
message: 'Data encrypted successfully'
});
return {
encrypted,
iv: iv.toString('hex'),
salt: salt.toString('hex'),
authTag
};
}
catch (error) {
this.logger.error('Encryption error:', error);
this.logSecurityEvent({
type: 'error',
severity: 'high',
message: `Encryption error: ${error instanceof Error ? error.message : 'Unknown error'}`
});
throw error;
}
}
/**
* Decrypt sensitive data
*/
decrypt(encryptedData, iv, salt, password, authTag) {
try {
const ivBuffer = Buffer.from(iv, 'hex');
const saltBuffer = Buffer.from(salt, 'hex');
// Derive key from password or use default
const key = password
? pbkdf2Sync(password, saltBuffer, this.config.encryption.iterations, 32, 'sha256')
: pbkdf2Sync('default-key', saltBuffer, this.config.encryption.iterations, 32, 'sha256');
const decipher = createDecipheriv(this.config.encryption.algorithm, key, ivBuffer);
if (authTag && decipher.setAuthTag) {
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
}
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
this.logSecurityEvent({
type: 'encryption',
severity: 'low',
message: 'Data decrypted successfully'
});
return decrypted;
}
catch (error) {
this.logger.error('Decryption error:', error);
this.logSecurityEvent({
type: 'error',
severity: 'high',
message: `Decryption error: ${error instanceof Error ? error.message : 'Unknown error'}`
});
throw error;
}
}
/**
* Hash password with salt
*/
hashPassword(password, salt) {
return pbkdf2Sync(password, salt, this.config.encryption.iterations, 64, 'sha256').toString('hex');
}
/**
* Generate secure random salt
*/
generateSalt() {
return randomBytes(this.config.encryption.saltLength).toString('hex');
}
/**
* Create new user session
*/
async createSession(userId, ipAddress, userAgent) {
const sessionId = randomBytes(16).toString('hex');
const token = randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + this.config.authentication.tokenExpiry * 1000);
const session = {
id: sessionId,
userId,
token,
expiresAt,
createdAt: new Date(),
lastActivity: new Date(),
ipAddress,
userAgent,
isValid: true
};
this.sessions.set(sessionId, session);
return session;
}
/**
* Check user permission through roles
*/
async checkUserPermission(user, resource, action, context) {
// Get all permissions for user's roles
const userPermissions = this.getUserPermissions(user.roles);
// Check if user has the required permission
return userPermissions.some(permission => permission.resource === resource &&
permission.action === action &&
this.checkPermissionConditions(permission.conditions, context));
}
/**
* Get all permissions for given roles
*/
getUserPermissions(roles) {
const permissions = [];
const processedRoles = new Set();
const processRole = (roleName) => {
if (processedRoles.has(roleName))
return;
processedRoles.add(roleName);
const roleConfig = this.config.authorization.roleHierarchy[roleName];
if (roleConfig) {
// Add role's own permissions (simplified - in real implementation, load from config)
permissions.push(...this.getRolePermissions(roleName));
// Process inherited roles
const inheritedRoles = roleConfig;
inheritedRoles.forEach(inheritedRole => processRole(inheritedRole));
}
};
roles.forEach(role => processRole(role));
return permissions;
}
/**
* Get permissions for a specific role
*/
getRolePermissions(roleName) {
// Default role permissions (in real implementation, load from configuration)
const defaultPermissions = {
'admin': [
{ resource: '*', action: '*' },
],
'user': [
{ resource: 'validation', action: 'read' },
{ resource: 'validation', action: 'execute' },
{ resource: 'monitoring', action: 'read' },
],
'viewer': [
{ resource: 'validation', action: 'read' },
{ resource: 'monitoring', action: 'read' },
]
};
return defaultPermissions[roleName] || [];
}
/**
* Check permission conditions
*/
checkPermissionConditions(conditions, context) {
if (!conditions)
return true;
if (!context)
return false;
// Simple condition checking (can be extended)
for (const [key, value] of Object.entries(conditions)) {
if (context[key] !== value) {
return false;
}
}
return true;
}
/**
* Check rate limiting
*/
checkRateLimit(identifier) {
if (!this.config.rateLimit.enabled)
return true;
const now = Date.now();
const entry = this.rateLimitStore.get(identifier);
if (!entry || entry.resetTime <= now) {
// Reset or create new entry
this.rateLimitStore.set(identifier, {
count: 1,
resetTime: now + this.config.rateLimit.windowMs
});
return true;
}
if (entry.count >= this.config.rateLimit.maxRequests) {
return false;
}
entry.count++;
return true;
}
/**
* Log security event
*/
logSecurityEvent(event) {
const securityEvent = {
id: randomBytes(8).toString('hex'),
timestamp: new Date(),
...event
};
this.securityEvents.push(securityEvent);
// Log to system logger based on severity
switch (event.severity) {
case 'critical':
this.logger.error(`[SECURITY] ${event.message}`, event.metadata);
break;
case 'high':
this.logger.error(`[SECURITY] ${event.message}`, event.metadata);
break;
case 'medium':
this.logger.warn(`[SECURITY] ${event.message}`, event.metadata);
break;
case 'low':
this.logger.info(`[SECURITY] ${event.message}`, event.metadata);
break;
}
// Emit event for external listeners
this.emit('securityEvent', securityEvent);
// Clean up old events based on retention policy
this.cleanupOldEvents();
}
/**
* Clean up old security events
*/
cleanupOldEvents() {
if (!this.config.audit.enabled)
return;
const cutoffDate = new Date(Date.now() - this.config.audit.retentionDays * 24 * 60 * 60 * 1000);
this.securityEvents = this.securityEvents.filter(event => event.timestamp > cutoffDate);
}
/**
* Setup cleanup intervals
*/
setupCleanupIntervals() {
// Clean up expired sessions every 5 minutes
const sessionCleanup = setInterval(() => {
const now = new Date();
for (const [sessionId, session] of this.sessions.entries()) {
if (session.expiresAt < now || !session.isValid) {
this.sessions.delete(sessionId);
}
}
}, 5 * 60 * 1000);
this.cleanupIntervals.push(sessionCleanup);
// Clean up rate limit store every hour
const rateLimitCleanup = setInterval(() => {
const now = Date.now();
for (const [identifier, entry] of this.rateLimitStore.entries()) {
if (entry.resetTime <= now) {
this.rateLimitStore.delete(identifier);
}
}
}, 60 * 60 * 1000);
this.cleanupIntervals.push(rateLimitCleanup);
// Clean up old security events daily
const eventsCleanup = setInterval(() => {
this.cleanupOldEvents();
}, 24 * 60 * 60 * 1000);
this.cleanupIntervals.push(eventsCleanup);
}
/**
* Load users from storage (placeholder - implement with actual storage)
*/
async loadUsersFromStorage() {
// In real implementation, load from database or file system
this.logger.debug('Loading users from storage...');
}
/**
* Load sessions from storage (placeholder - implement with actual storage)
*/
async loadSessionsFromStorage() {
// In real implementation, load from database or file system
this.logger.debug('Loading sessions from storage...');
}
/**
* Create default admin user if none exists
*/
async createDefaultAdminUser() {
if (this.users.size === 0) {
const adminId = randomBytes(8).toString('hex');
const salt = this.generateSalt();
const defaultPassword = 'admin123'; // In real implementation, generate secure password
const adminUser = {
id: adminId,
username: 'admin',
email: 'admin@localhost',
passwordHash: this.hashPassword(defaultPassword, salt),
salt,
roles: ['admin'],
isActive: true,
failedLoginAttempts: 0,
createdAt: new Date(),
updatedAt: new Date()
};
this.users.set(adminId, adminUser);
this.logger.warn('Default admin user created with username: admin, password: admin123');
this.logSecurityEvent({
type: 'access',
severity: 'medium',
message: 'Default admin user created',
userId: adminId
});
}
}
/**
* Validate and merge configuration with defaults
*/
validateAndMergeConfig(config) {
const defaultConfig = {
authentication: {
enabled: true,
tokenExpiry: 3600, // 1 hour
maxLoginAttempts: 5,
lockoutDuration: 900, // 15 minutes
requireTwoFactor: false
},
authorization: {
enabled: true,
defaultRole: 'user',
roleHierarchy: {
'admin': ['user', 'viewer'],
'user': ['viewer'],
'viewer': []
}
},
encryption: {
algorithm: 'aes-256-gcm',
keyLength: 256,
ivLength: 12,
saltLength: 32,
iterations: 100000
},
rateLimit: {
enabled: true,
windowMs: 15 * 60 * 1000, // 15 minutes
maxRequests: 100,
skipSuccessfulRequests: false
},
audit: {
enabled: true,
logLevel: 'standard',
retentionDays: 30
}
};
return {
authentication: { ...defaultConfig.authentication, ...config.authentication },
authorization: { ...defaultConfig.authorization, ...config.authorization },
encryption: { ...defaultConfig.encryption, ...config.encryption },
rateLimit: { ...defaultConfig.rateLimit, ...config.rateLimit },
audit: { ...defaultConfig.audit, ...config.audit }
};
}
/**
* Get security statistics
*/
getSecurityStats() {
const activeUsers = Array.from(this.users.values()).filter(u => u.isActive).length;
const activeSessions = Array.from(this.sessions.values()).filter(s => s.isValid && s.expiresAt > new Date()).length;
const lastSecurityEvent = this.securityEvents[this.securityEvents.length - 1];
return {
activeUsers,
activeSessions,
securityEvents: this.securityEvents.length,
rateLimitEntries: this.rateLimitStore.size,
lastSecurityEvent
};
}
/**
* Get security events with filtering
*/
getSecurityEvents(filter) {
let events = [...this.securityEvents];
if (filter) {
if (filter.type) {
events = events.filter(e => e.type === filter.type);
}
if (filter.severity) {
events = events.filter(e => e.severity === filter.severity);
}
if (filter.userId) {
events = events.filter(e => e.userId === filter.userId);
}
if (filter.since) {
events = events.filter(e => e.timestamp >= filter.since);
}
}
// Sort by timestamp (newest first)
events.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
if (filter?.limit) {
events = events.slice(0, filter.limit);
}
return events;
}
/**
* Revoke session
*/
async revokeSession(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
session.isValid = false;
this.logSecurityEvent({
type: 'authentication',
severity: 'low',
message: 'Session revoked',
sessionId,
userId: session.userId
});
return true;
}
return false;
}
/**
* Revoke all sessions for a user
*/
async revokeUserSessions(userId) {
let revokedCount = 0;
for (const session of this.sessions.values()) {
if (session.userId === userId && session.isValid) {
session.isValid = false;
revokedCount++;
}
}
if (revokedCount > 0) {
this.logSecurityEvent({
type: 'authentication',
severity: 'medium',
message: `All user sessions revoked (${revokedCount} sessions)`,
userId
});
}
return revokedCount;
}
/**
* Sanitize input to prevent injection attacks
*/
sanitizeInput(input) {
if (typeof input !== 'string') {
return '';
}
// Basic sanitization - remove potentially dangerous characters
return input
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/"/g, '"') // Escape double quotes
.replace(/'/g, "'") // Escape single quotes
.replace(/[;&|`$]/g, '') // Remove command injection characters
.trim();
}
/**
* Validate input against patterns
*/
validateInput(input, pattern) {
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
username: /^[a-zA-Z0-9_]{3,20}$/,
password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
alphanumeric: /^[a-zA-Z0-9]+$/
};
return patterns[pattern]?.test(input) || false;
}
/**
* Shutdown security manager
*/
async shutdown() {
this.logger.info('Shutting down Security Manager...');
// Clear all cleanup intervals
for (const interval of this.cleanupIntervals) {
clearInterval(interval);
}
this.cleanupIntervals.length = 0;
// Clear all intervals and cleanup
this.removeAllListeners();
this.sessions.clear();
this.rateLimitStore.clear();
this.logSecurityEvent({
type: 'access',
severity: 'low',
message: 'Security Manager shutdown'
});
this.isInitialized = false;
this.logger.info('Security Manager shutdown complete');
}
}
//# sourceMappingURL=index.js.map