Skip to main content
Glama

Electron MCP Server

by halilural
audit.tsโ€ข8.48 kB
import { promises as fs, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs'; import { join } from 'path'; import { createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto'; import { logger } from '../utils/logger'; export interface AuditLogEntry { timestamp: string; sessionId: string; action: string; command?: string; riskLevel: 'low' | 'medium' | 'high' | 'critical'; success: boolean; error?: string; executionTime: number; sourceIP?: string; userAgent?: string; } export interface SecurityMetrics { totalRequests: number; blockedRequests: number; highRiskRequests: number; criticalRiskRequests: number; averageExecutionTime: number; topCommands: { command: string; count: number }[]; errorRate: number; } export class SecurityLogger { private logDir: string; private encryptionKey: Buffer; constructor(logDir: string = 'logs/security') { this.logDir = logDir; this.encryptionKey = this.getOrCreateEncryptionKey(); // Note: ensureLogDirectory is called in logSecurityEvent to handle async properly } async logSecurityEvent(entry: AuditLogEntry): Promise<void> { try { // Ensure directory exists before writing await this.ensureLogDirectory(); const logFile = this.getLogFilePath(new Date()); const encryptedEntry = this.encryptLogEntry(entry); const logLine = JSON.stringify(encryptedEntry) + '\n'; await fs.appendFile(logFile, logLine, 'utf8'); // Also log to console for immediate monitoring const logLevel = this.getLogLevel(entry.riskLevel); logger[logLevel]( `Security Event [${entry.action}]: ${entry.success ? 'SUCCESS' : 'BLOCKED'}`, { sessionId: entry.sessionId, riskLevel: entry.riskLevel, executionTime: entry.executionTime, }, ); } catch (error) { logger.error('Failed to write security log:', error); } } async getSecurityMetrics(since?: Date): Promise<SecurityMetrics> { try { const entries = await this.readLogEntries(since); const totalRequests = entries.length; const blockedRequests = entries.filter((e) => !e.success).length; const highRiskRequests = entries.filter((e) => e.riskLevel === 'high').length; const criticalRiskRequests = entries.filter((e) => e.riskLevel === 'critical').length; const totalExecutionTime = entries.reduce((sum, e) => sum + e.executionTime, 0); const averageExecutionTime = totalRequests > 0 ? totalExecutionTime / totalRequests : 0; const commandCounts = new Map<string, number>(); entries.forEach((e) => { if (e.command) { const truncated = e.command.substring(0, 50); commandCounts.set(truncated, (commandCounts.get(truncated) || 0) + 1); } }); const topCommands = Array.from(commandCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([command, count]) => ({ command, count })); const errorRate = totalRequests > 0 ? blockedRequests / totalRequests : 0; return { totalRequests, blockedRequests, highRiskRequests, criticalRiskRequests, averageExecutionTime, topCommands, errorRate, }; } catch (error) { logger.error('Failed to generate security metrics:', error); throw error; } } async searchLogs(criteria: { action?: string; riskLevel?: string; since?: Date; until?: Date; limit?: number; }): Promise<AuditLogEntry[]> { try { const entries = await this.readLogEntries(criteria.since, criteria.until); let filtered = entries; if (criteria.action) { filtered = filtered.filter((e) => e.action === criteria.action); } if (criteria.riskLevel) { filtered = filtered.filter((e) => e.riskLevel === criteria.riskLevel); } if (criteria.limit) { filtered = filtered.slice(0, criteria.limit); } return filtered; } catch (error) { logger.error('Failed to search security logs:', error); throw error; } } private async ensureLogDirectory(): Promise<void> { try { await fs.mkdir(this.logDir, { recursive: true }); } catch (error) { logger.error('Failed to create log directory:', error); } } private getOrCreateEncryptionKey(): Buffer { const keyPath = join(this.logDir, '.security-key'); try { // Try to read existing key const keyData = readFileSync(keyPath); return Buffer.from(keyData); } catch { // Generate new key const key = randomBytes(32); try { // Ensure directory exists before writing key mkdirSync(this.logDir, { recursive: true }); writeFileSync(keyPath, key); // Restrict permissions on the key file chmodSync(keyPath, 0o600); } catch (error) { logger.warn('Failed to save encryption key:', error); } return key; } } private encryptLogEntry(entry: AuditLogEntry): any { const sensitiveFields = ['command', 'error', 'sourceIP', 'userAgent']; const encrypted: any = { ...entry }; for (const field of sensitiveFields) { if (encrypted[field]) { const value = String(encrypted[field]); encrypted[field] = this.encryptString(value); } } return encrypted; } private encryptString(text: string): string { try { const iv = randomBytes(16); const cipher = createCipheriv('aes-256-cbc', this.encryptionKey, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } catch { // Fallback to hash if encryption fails return createHash('sha256').update(text).digest('hex'); } } private decryptString(encryptedText: string): string { try { const parts = encryptedText.split(':'); if (parts.length !== 2) return '[ENCRYPTED]'; const iv = Buffer.from(parts[0], 'hex'); const encrypted = parts[1]; const decipher = createDecipheriv('aes-256-cbc', this.encryptionKey, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch { return '[ENCRYPTED]'; } } private getLogFilePath(date: Date): string { const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD return join(this.logDir, `security-${dateStr}.log`); } private getLogLevel(riskLevel: string): 'info' | 'warn' | 'error' { switch (riskLevel) { case 'critical': return 'error'; case 'high': return 'error'; case 'medium': return 'warn'; default: return 'info'; } } private async readLogEntries(since?: Date, until?: Date): Promise<AuditLogEntry[]> { const entries: AuditLogEntry[] = []; const now = new Date(); const startDate = since || new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago const endDate = until || now; // Read log files for the date range const currentDate = new Date(startDate); while (currentDate <= endDate) { const logFile = this.getLogFilePath(currentDate); try { const content = await fs.readFile(logFile, 'utf8'); const lines = content.trim().split('\n'); for (const line of lines) { if (line.trim()) { try { const entry = JSON.parse(line); entries.push(this.decryptLogEntry(entry)); } catch (parseError) { logger.warn('Failed to parse log entry:', parseError); } } } } catch { // File doesn't exist or can't be read - skip silently } currentDate.setDate(currentDate.getDate() + 1); } return entries.sort( (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), ); } private decryptLogEntry(entry: any): AuditLogEntry { const sensitiveFields = ['command', 'error', 'sourceIP', 'userAgent']; const decrypted = { ...entry }; for (const field of sensitiveFields) { if (decrypted[field]) { decrypted[field] = this.decryptString(decrypted[field]); } } return decrypted as AuditLogEntry; } } // Global security logger instance export const securityLogger = new SecurityLogger();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/halilural/electron-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server