Skip to main content
Glama

Curupira

by drzln
audit.ts6.25 kB
/** * Audit logging module * * Tracks all security-relevant actions */ import { logger } from '../config/logger.js' import type { FastifyRequest } from 'fastify' export interface AuditEvent { timestamp: string eventType: AuditEventType userId?: string ip?: string userAgent?: string action: string resource?: string result: 'success' | 'failure' | 'blocked' details?: Record<string, any> error?: string } export type AuditEventType = | 'auth.login' | 'auth.logout' | 'auth.failed' | 'resource.read' | 'tool.execute' | 'cdp.command' | 'security.blocked' | 'rate.limited' | 'error.internal' export interface AuditConfig { enabled: boolean logToFile?: boolean filePath?: string maxFileSize?: number maxFiles?: number includeRequestBody?: boolean includeResponseBody?: boolean sensitiveFields?: string[] } export class AuditLogger { private config: AuditConfig private buffer: AuditEvent[] = [] private bufferSize = 1000 constructor(config: AuditConfig) { this.config = config } /** * Log an audit event */ log(event: Omit<AuditEvent, 'timestamp'>) { if (!this.config.enabled) { return } const auditEvent: AuditEvent = { ...event, timestamp: new Date().toISOString(), } // Add to buffer this.buffer.push(auditEvent) if (this.buffer.length > this.bufferSize) { this.buffer.shift() } // Log to system logger logger.info({ audit: auditEvent }, 'Audit event') // TODO: If file logging is enabled, write to file if (this.config.logToFile && this.config.filePath) { this.writeToFile(auditEvent) } } /** * Log authentication event */ logAuth( request: FastifyRequest, action: 'login' | 'logout' | 'failed', userId?: string, error?: string ) { this.log({ eventType: `auth.${action}`, userId, ip: request.ip, userAgent: request.headers['user-agent'], action: `Authentication ${action}`, result: action === 'failed' ? 'failure' : 'success', error, }) } /** * Log resource access */ logResourceAccess( request: FastifyRequest, resourceUri: string, success: boolean, error?: string ) { const user = (request as any).user this.log({ eventType: 'resource.read', userId: user?.id, ip: request.ip, userAgent: request.headers['user-agent'], action: 'Read resource', resource: resourceUri, result: success ? 'success' : 'failure', error, }) } /** * Log tool execution */ logToolExecution( request: FastifyRequest, toolName: string, args: any, success: boolean, error?: string ) { const user = (request as any).user this.log({ eventType: 'tool.execute', userId: user?.id, ip: request.ip, userAgent: request.headers['user-agent'], action: `Execute tool: ${toolName}`, resource: toolName, result: success ? 'success' : 'failure', details: this.config.includeRequestBody ? { args } : undefined, error, }) } /** * Log CDP command */ logCDPCommand( method: string, params: any, success: boolean, userId?: string, error?: string ) { this.log({ eventType: 'cdp.command', userId, action: `CDP command: ${method}`, resource: method, result: success ? 'success' : 'failure', details: this.config.includeRequestBody ? { params } : undefined, error, }) } /** * Log security block */ logSecurityBlock( request: FastifyRequest, reason: string, details?: Record<string, any> ) { const user = (request as any).user this.log({ eventType: 'security.blocked', userId: user?.id, ip: request.ip, userAgent: request.headers['user-agent'], action: 'Security block', result: 'blocked', details: { reason, ...details }, }) } /** * Log rate limit */ logRateLimit(request: FastifyRequest, endpoint: string) { const user = (request as any).user this.log({ eventType: 'rate.limited', userId: user?.id, ip: request.ip, userAgent: request.headers['user-agent'], action: 'Rate limit exceeded', resource: endpoint, result: 'blocked', }) } /** * Get recent audit events */ getRecentEvents(count: number = 100): AuditEvent[] { return this.buffer.slice(-count) } /** * Get events by type */ getEventsByType(eventType: AuditEventType, count: number = 100): AuditEvent[] { return this.buffer .filter(event => event.eventType === eventType) .slice(-count) } /** * Get events by user */ getEventsByUser(userId: string, count: number = 100): AuditEvent[] { return this.buffer .filter(event => event.userId === userId) .slice(-count) } /** * Get failed events */ getFailedEvents(count: number = 100): AuditEvent[] { return this.buffer .filter(event => event.result === 'failure' || event.result === 'blocked') .slice(-count) } /** * Write to file (placeholder) */ private writeToFile(event: AuditEvent) { // TODO: Implement file writing with rotation // For now, just log that we would write to file logger.debug({ file: this.config.filePath, event }, 'Would write audit event to file') } /** * Get statistics */ getStatistics() { const stats = { totalEvents: this.buffer.length, byType: {} as Record<AuditEventType, number>, byResult: { success: 0, failure: 0, blocked: 0, }, recentFailures: 0, } for (const event of this.buffer) { // Count by type stats.byType[event.eventType] = (stats.byType[event.eventType] || 0) + 1 // Count by result stats.byResult[event.result]++ // Count recent failures (last 5 minutes) const fiveMinutesAgo = Date.now() - 5 * 60 * 1000 if ( event.result !== 'success' && new Date(event.timestamp).getTime() > fiveMinutesAgo ) { stats.recentFailures++ } } return stats } }

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/drzln/curupira'

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