anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
720 lines • 26.5 kB
JavaScript
;
/**
* 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