UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

720 lines 26.5 kB
"use strict"; /** * Audit Logger for MCP * * Comprehensive audit logging for all LLM interactions with compliance support */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AuditLogger = exports.AuditEventType = void 0; const events_1 = require("events"); const crypto = __importStar(require("crypto")); const types_1 = require("../types"); // Using MemoryStorageProvider directly for audit log storage const memory_storage_provider_1 = require("../../storage/providers/memory-storage-provider"); /** * Audit event types */ var AuditEventType; (function (AuditEventType) { // Authentication events AuditEventType["AUTH_SUCCESS"] = "auth.success"; AuditEventType["AUTH_FAILURE"] = "auth.failure"; AuditEventType["AUTH_TOKEN_CREATED"] = "auth.token.created"; AuditEventType["AUTH_TOKEN_REFRESHED"] = "auth.token.refreshed"; AuditEventType["AUTH_SESSION_CREATED"] = "auth.session.created"; AuditEventType["AUTH_SESSION_EXPIRED"] = "auth.session.expired"; // Authorization events AuditEventType["AUTHZ_GRANTED"] = "authz.granted"; AuditEventType["AUTHZ_DENIED"] = "authz.denied"; AuditEventType["PERMISSION_CHANGED"] = "permission.changed"; // LLM interaction events AuditEventType["LLM_REQUEST"] = "llm.request"; AuditEventType["LLM_RESPONSE"] = "llm.response"; AuditEventType["LLM_ERROR"] = "llm.error"; AuditEventType["LLM_FUNCTION_CALL"] = "llm.function_call"; AuditEventType["LLM_STREAMING_START"] = "llm.streaming.start"; AuditEventType["LLM_STREAMING_END"] = "llm.streaming.end"; // Provider events AuditEventType["PROVIDER_CONNECTED"] = "provider.connected"; AuditEventType["PROVIDER_DISCONNECTED"] = "provider.disconnected"; AuditEventType["PROVIDER_ERROR"] = "provider.error"; AuditEventType["PROVIDER_FAILOVER"] = "provider.failover"; // Security events AuditEventType["SECURITY_ALERT"] = "security.alert"; AuditEventType["RATE_LIMIT_EXCEEDED"] = "rate_limit.exceeded"; AuditEventType["CREDENTIAL_ACCESSED"] = "credential.accessed"; AuditEventType["CREDENTIAL_ROTATED"] = "credential.rotated"; // System events AuditEventType["SYSTEM_START"] = "system.start"; AuditEventType["SYSTEM_SHUTDOWN"] = "system.shutdown"; AuditEventType["CONFIG_CHANGED"] = "config.changed"; })(AuditEventType || (exports.AuditEventType = AuditEventType = {})); /** * Audit Logger */ class AuditLogger extends events_1.EventEmitter { constructor(config, storageProvider) { super(); this.config = config; this.logs = new Map(); this.indexByAgent = new Map(); this.indexBySession = new Map(); this.indexByDate = new Map(); this.hashChain = null; this.storageProvider = storageProvider || new memory_storage_provider_1.MemoryStorageProvider(); this.loadLogs(); this.startRetentionCleanup(); } /** * Log LLM request */ async logRequest(request, agentDID, sessionId) { const entry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), eventType: AuditEventType.LLM_REQUEST, agentDID, sessionId, requestId: request.id, provider: request.metadata?.source, model: request.parameters?.model, action: request.type, status: 'success', metadata: { requestSize: JSON.stringify(request).length, tags: request.metadata?.tags } }; // Log sensitive data only if configured if (this.config.logSensitiveData) { entry.metadata.prompt = request.prompt; if (request.functions) { entry.metadata.functionCalls = request.functions.map(f => ({ name: f.name, arguments: {}, id: `func-${f.name}` })); } } return this.addLogEntry(entry); } /** * Log LLM response */ async logResponse(response, request, duration) { const entry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), eventType: AuditEventType.LLM_RESPONSE, agentDID: request.agentDID, sessionId: request.sessionId, requestId: request.id, provider: response.provider, model: response.model, action: request.type, status: response.status === 'success' ? 'success' : 'failure', duration, metadata: { responseSize: response.content ? response.content.length : 0, usage: response.usage, error: response.error?.message } }; // Log response content if configured if (this.config.logResponses && this.config.logSensitiveData) { entry.metadata.content = response.content; } if (response.functionCall) { entry.metadata.functionCalls = [response.functionCall]; } return this.addLogEntry(entry); } /** * Log authentication event */ async logAuthentication(agentDID, success, method, error) { const entry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), eventType: success ? AuditEventType.AUTH_SUCCESS : AuditEventType.AUTH_FAILURE, agentDID, action: `auth.${method}`, status: success ? 'success' : 'failure', metadata: { error } }; return this.addLogEntry(entry); } /** * Log authorization event */ async logAuthorization(agentDID, resource, action, granted, reasons) { const entry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), eventType: granted ? AuditEventType.AUTHZ_GRANTED : AuditEventType.AUTHZ_DENIED, agentDID, action, resource, status: granted ? 'success' : 'failure', metadata: { error: reasons?.join(', ') } }; return this.addLogEntry(entry); } /** * Log security alert */ async logSecurityAlert(type, agentDID, details) { const entry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), eventType: AuditEventType.SECURITY_ALERT, agentDID, action: `security.${type}`, status: 'error', metadata: { error: JSON.stringify(details) } }; this.emit('security_alert', entry); return this.addLogEntry(entry); } /** * Add log entry */ async addLogEntry(entry) { // Add hash chain for integrity if (this.hashChain) { entry.previousHash = this.hashChain; } entry.hash = this.calculateHash(entry); this.hashChain = entry.hash; // Store in memory this.logs.set(entry.id, entry); // Update indices this.updateIndices(entry); // Persist to storage await this.persistLog(entry); // Emit event this.emit('log_added', entry); return entry.id; } /** * Update indices */ updateIndices(entry) { // Agent index if (!this.indexByAgent.has(entry.agentDID)) { this.indexByAgent.set(entry.agentDID, new Set()); } this.indexByAgent.get(entry.agentDID).add(entry.id); // Session index if (entry.sessionId) { if (!this.indexBySession.has(entry.sessionId)) { this.indexBySession.set(entry.sessionId, new Set()); } this.indexBySession.get(entry.sessionId).add(entry.id); } // Date index const dateKey = entry.timestamp.toISOString().split('T')[0]; if (!this.indexByDate.has(dateKey)) { this.indexByDate.set(dateKey, new Set()); } this.indexByDate.get(dateKey).add(entry.id); } /** * Calculate hash for log entry */ calculateHash(entry) { const data = JSON.stringify({ id: entry.id, timestamp: entry.timestamp, eventType: entry.eventType, agentDID: entry.agentDID, action: entry.action, status: entry.status, previousHash: entry.previousHash }); return crypto.createHash('sha256').update(data).digest('hex'); } /** * Query audit logs */ async query(options = {}) { let results = []; // Start with all logs or filtered by indices if (options.agentDID) { const agentLogs = this.indexByAgent.get(options.agentDID) || new Set(); results = Array.from(agentLogs).map(id => this.logs.get(id)).filter(Boolean); } else if (options.sessionId) { const sessionLogs = this.indexBySession.get(options.sessionId) || new Set(); results = Array.from(sessionLogs).map(id => this.logs.get(id)).filter(Boolean); } else { results = Array.from(this.logs.values()); } // Apply filters if (options.startDate) { results = results.filter(log => log.timestamp >= options.startDate); } if (options.endDate) { results = results.filter(log => log.timestamp <= options.endDate); } if (options.eventTypes && options.eventTypes.length > 0) { results = results.filter(log => options.eventTypes.includes(log.eventType)); } if (options.status) { results = results.filter(log => log.status === options.status); } if (options.provider) { results = results.filter(log => log.provider === options.provider); } // Sort const sortBy = options.sortBy || 'timestamp'; const sortOrder = options.sortOrder || 'desc'; results.sort((a, b) => { let aVal, bVal; switch (sortBy) { case 'timestamp': aVal = a.timestamp.getTime(); bVal = b.timestamp.getTime(); break; case 'duration': aVal = a.duration || 0; bVal = b.duration || 0; break; case 'requestSize': aVal = a.metadata.requestSize || 0; bVal = b.metadata.requestSize || 0; break; } return sortOrder === 'asc' ? aVal - bVal : bVal - aVal; }); // Apply pagination if (options.offset) { results = results.slice(options.offset); } if (options.limit) { results = results.slice(0, options.limit); } return results; } /** * Get statistics */ async getStatistics(options = {}) { const logs = await this.query(options); const stats = { totalEvents: logs.length, eventsByType: {}, eventsByStatus: {}, eventsByProvider: {}, eventsByAgent: {}, averageResponseTime: 0, totalTokensUsed: 0, totalCost: 0, errorRate: 0 }; let totalDuration = 0; let durationCount = 0; let errorCount = 0; for (const log of logs) { // Count by type stats.eventsByType[log.eventType] = (stats.eventsByType[log.eventType] || 0) + 1; // Count by status stats.eventsByStatus[log.status] = (stats.eventsByStatus[log.status] || 0) + 1; // Count by provider if (log.provider) { stats.eventsByProvider[log.provider] = (stats.eventsByProvider[log.provider] || 0) + 1; } // Count by agent stats.eventsByAgent[log.agentDID] = (stats.eventsByAgent[log.agentDID] || 0) + 1; // Calculate averages if (log.duration) { totalDuration += log.duration; durationCount++; } // Sum usage if (log.metadata.usage) { stats.totalTokensUsed += log.metadata.usage.totalTokens || 0; stats.totalCost += log.metadata.usage.cost || 0; } // Count errors if (log.status === 'error' || log.status === 'failure') { errorCount++; } } stats.averageResponseTime = durationCount > 0 ? totalDuration / durationCount : 0; stats.errorRate = logs.length > 0 ? errorCount / logs.length : 0; return stats; } /** * Generate compliance report */ async generateComplianceReport(startDate, endDate) { const logs = await this.query({ startDate, endDate }); const report = { period: { start: startDate, end: endDate }, summary: { totalRequests: 0, successRate: 0, averageResponseTime: 0, totalCost: 0, uniqueAgents: new Set(), topAgents: [], topModels: [] }, security: { authFailures: 0, authzDenials: 0, securityAlerts: 0, rateLimitViolations: 0 }, usage: { totalTokens: 0, tokensByProvider: {}, costByProvider: {}, functionCalls: 0 }, compliance: { dataRetentionCompliant: true, auditTrailIntegrity: true, unauthorizedAccessAttempts: 0 } }; const agentCounts = new Map(); const modelCounts = new Map(); let successCount = 0; let totalDuration = 0; let durationCount = 0; for (const log of logs) { // Count requests if (log.eventType === AuditEventType.LLM_REQUEST) { report.summary.totalRequests++; } // Track unique agents report.summary.uniqueAgents.add(log.agentDID); agentCounts.set(log.agentDID, (agentCounts.get(log.agentDID) || 0) + 1); // Track models if (log.model) { modelCounts.set(log.model, (modelCounts.get(log.model) || 0) + 1); } // Count successes if (log.status === 'success') { successCount++; } // Calculate response times if (log.duration) { totalDuration += log.duration; durationCount++; } // Sum costs if (log.metadata.usage?.cost) { report.summary.totalCost += log.metadata.usage.cost; if (log.provider) { report.usage.costByProvider[log.provider] = (report.usage.costByProvider[log.provider] || 0) + log.metadata.usage.cost; } } // Count tokens if (log.metadata.usage?.totalTokens) { report.usage.totalTokens += log.metadata.usage.totalTokens; if (log.provider) { report.usage.tokensByProvider[log.provider] = (report.usage.tokensByProvider[log.provider] || 0) + log.metadata.usage.totalTokens; } } // Count function calls if (log.metadata.functionCalls) { report.usage.functionCalls += log.metadata.functionCalls.length; } // Security events switch (log.eventType) { case AuditEventType.AUTH_FAILURE: report.security.authFailures++; report.compliance.unauthorizedAccessAttempts++; break; case AuditEventType.AUTHZ_DENIED: report.security.authzDenials++; break; case AuditEventType.SECURITY_ALERT: report.security.securityAlerts++; break; case AuditEventType.RATE_LIMIT_EXCEEDED: report.security.rateLimitViolations++; break; } // Check audit trail integrity if (log.hash && log.previousHash) { // Verify hash chain const calculatedHash = this.calculateHash({ ...log, hash: undefined }); if (calculatedHash !== log.hash) { report.compliance.auditTrailIntegrity = false; } } } // Finalize summary report.summary.uniqueAgents = report.summary.uniqueAgents.size; report.summary.successRate = report.summary.totalRequests > 0 ? successCount / report.summary.totalRequests : 0; report.summary.averageResponseTime = durationCount > 0 ? totalDuration / durationCount : 0; // Top agents report.summary.topAgents = Array.from(agentCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([agentDID, requests]) => ({ agentDID, requests })); // Top models report.summary.topModels = Array.from(modelCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([model, requests]) => ({ model, requests })); // Check retention compliance const oldestLog = logs.reduce((oldest, log) => log.timestamp < oldest.timestamp ? log : oldest, logs[0] || { timestamp: new Date() }); const retentionDays = (Date.now() - oldestLog.timestamp.getTime()) / (1000 * 60 * 60 * 24); report.compliance.dataRetentionCompliant = retentionDays <= (this.config.retentionPeriod / (1000 * 60 * 60 * 24)); return report; } /** * Export logs */ async export(format, options = {}) { const logs = await this.query(options); switch (format) { case types_1.AuditExportFormat.JSON: return JSON.stringify(logs, null, 2); case types_1.AuditExportFormat.CSV: return this.exportCSV(logs); case types_1.AuditExportFormat.SYSLOG: return this.exportSyslog(logs); default: throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_REQUEST, message: `Unsupported export format: ${format}`, timestamp: new Date(), retryable: false }); } } /** * Export as CSV */ exportCSV(logs) { const headers = [ 'ID', 'Timestamp', 'Event Type', 'Agent DID', 'Session ID', 'Action', 'Resource', 'Status', 'Duration', 'Provider', 'Model', 'Error' ]; const rows = logs.map(log => [ log.id, log.timestamp.toISOString(), log.eventType, log.agentDID, log.sessionId || '', log.action, log.resource || '', log.status, log.duration || '', log.provider || '', log.model || '', log.metadata.error || '' ]); return [ headers.join(','), ...rows.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')) ].join('\n'); } /** * Export as Syslog */ exportSyslog(logs) { return logs.map(log => { const severity = log.status === 'error' ? 3 : log.status === 'failure' ? 4 : 6; const facility = 16; // Local0 const priority = facility * 8 + severity; return `<${priority}>${log.timestamp.toISOString()} ${log.agentDID} ${log.eventType} - ${JSON.stringify(log)}`; }).join('\n'); } /** * Load logs from storage */ async loadLogs() { try { // Use internal storage for audit logs const storedLogs = this.storageProvider._storage?.get('mcp:audit:logs'); if (storedLogs) { const logs = JSON.parse(storedLogs); for (const log of logs) { // Convert dates log.timestamp = new Date(log.timestamp); this.logs.set(log.id, log); this.updateIndices(log); // Update hash chain if (log.hash) { this.hashChain = log.hash; } } } } catch (error) { this.emit('error', new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CONFIG, message: `Failed to load audit logs: ${error.message}`, timestamp: new Date(), retryable: false })); } } /** * Persist log to storage */ async persistLog(entry) { if (!this.config.enabled) return; try { // Get all logs for persistence const allLogs = Array.from(this.logs.values()); // Limit to retention period const cutoffDate = new Date(Date.now() - this.config.retentionPeriod); const logsToKeep = allLogs.filter(log => log.timestamp > cutoffDate); // Use internal storage for audit logs this.storageProvider._storage = this.storageProvider._storage || new Map(); this.storageProvider._storage.set('mcp:audit:logs', JSON.stringify(logsToKeep)); } catch (error) { this.emit('error', new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_ERROR, message: `Failed to persist audit log: ${error.message}`, timestamp: new Date(), retryable: true })); } } /** * Start retention cleanup timer */ startRetentionCleanup() { if (!this.config.retentionPeriod) return; this.retentionTimer = setInterval(() => { this.cleanupOldLogs(); }, 60 * 60 * 1000); // Run every hour } /** * Clean up old logs */ cleanupOldLogs() { const cutoffDate = new Date(Date.now() - this.config.retentionPeriod); const logsToDelete = []; for (const [id, log] of this.logs.entries()) { if (log.timestamp < cutoffDate) { logsToDelete.push(id); } } for (const id of logsToDelete) { const log = this.logs.get(id); this.logs.delete(id); // Remove from indices this.indexByAgent.get(log.agentDID)?.delete(id); if (log.sessionId) { this.indexBySession.get(log.sessionId)?.delete(id); } const dateKey = log.timestamp.toISOString().split('T')[0]; this.indexByDate.get(dateKey)?.delete(id); } if (logsToDelete.length > 0) { this.emit('logs_cleaned', logsToDelete.length); } } /** * Verify audit trail integrity */ async verifyIntegrity() { const errors = []; let previousHash = null; const sortedLogs = Array.from(this.logs.values()) .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); for (const log of sortedLogs) { // Check hash chain if (log.previousHash !== previousHash) { errors.push(`Hash chain broken at log ${log.id}`); } // Verify hash const calculatedHash = this.calculateHash({ ...log, hash: undefined }); if (calculatedHash !== log.hash) { errors.push(`Invalid hash for log ${log.id}`); } previousHash = log.hash || null; } return { valid: errors.length === 0, errors }; } /** * Shutdown audit logger */ shutdown() { if (this.retentionTimer) { clearInterval(this.retentionTimer); } // Final persist this.persistLog({}).catch(() => { }); this.logs.clear(); this.indexByAgent.clear(); this.indexBySession.clear(); this.indexByDate.clear(); this.removeAllListeners(); } } exports.AuditLogger = AuditLogger; exports.default = AuditLogger; //# sourceMappingURL=audit-logger.js.map