package security import ( "context" "fmt" "regexp" "runtime" "strings" "time" "github.com/fraktal/mev-beta/internal/logger" ) // SecureError represents a security-aware error with context type SecureError struct { Code string `json:"code"` Message string `json:"message"` Timestamp time.Time `json:"timestamp"` Context map[string]interface{} `json:"context,omitempty"` Stack []StackFrame `json:"stack,omitempty"` Wrapped error `json:"-"` Sensitive bool `json:"sensitive"` Category ErrorCategory `json:"category"` Severity ErrorSeverity `json:"severity"` } // StackFrame represents a single frame in the call stack type StackFrame struct { Function string `json:"function"` File string `json:"file"` Line int `json:"line"` } // ErrorCategory defines categories of errors type ErrorCategory string const ( ErrorCategoryAuthentication ErrorCategory = "authentication" ErrorCategoryAuthorization ErrorCategory = "authorization" ErrorCategoryValidation ErrorCategory = "validation" ErrorCategoryRateLimit ErrorCategory = "rate_limit" ErrorCategoryCircuitBreaker ErrorCategory = "circuit_breaker" ErrorCategoryEncryption ErrorCategory = "encryption" ErrorCategoryNetwork ErrorCategory = "network" ErrorCategoryTransaction ErrorCategory = "transaction" ErrorCategoryInternal ErrorCategory = "internal" ) // ErrorSeverity defines error severity levels type ErrorSeverity string const ( ErrorSeverityLow ErrorSeverity = "low" ErrorSeverityMedium ErrorSeverity = "medium" ErrorSeverityHigh ErrorSeverity = "high" ErrorSeverityCritical ErrorSeverity = "critical" ) // ErrorHandler provides secure error handling with context preservation type ErrorHandler struct { enableStackTrace bool sensitiveFields map[string]bool errorMetrics *ErrorMetrics logger *logger.Logger } // ErrorMetrics tracks error statistics type ErrorMetrics struct { TotalErrors int64 `json:"total_errors"` ErrorsByCategory map[ErrorCategory]int64 `json:"errors_by_category"` ErrorsBySeverity map[ErrorSeverity]int64 `json:"errors_by_severity"` SensitiveDataLeaks int64 `json:"sensitive_data_leaks"` } // NewErrorHandler creates a new secure error handler func NewErrorHandler(enableStackTrace bool) *ErrorHandler { return &ErrorHandler{ enableStackTrace: enableStackTrace, sensitiveFields: map[string]bool{ "password": true, "private_key": true, "secret": true, "token": true, "seed": true, "mnemonic": true, "api_key": true, "private": true, }, errorMetrics: &ErrorMetrics{ ErrorsByCategory: make(map[ErrorCategory]int64), ErrorsBySeverity: make(map[ErrorSeverity]int64), }, logger: logger.New("info", "json", "logs/errors.log"), } } // WrapError wraps an error with security context func (eh *ErrorHandler) WrapError(err error, code string, message string, category ErrorCategory, severity ErrorSeverity) *SecureError { if err == nil { return nil } secureErr := &SecureError{ Code: code, Message: message, Timestamp: time.Now(), Wrapped: err, Category: category, Severity: severity, Context: make(map[string]interface{}), } // Capture stack trace if enabled if eh.enableStackTrace { secureErr.Stack = eh.captureStackTrace() } // Check for sensitive data secureErr.Sensitive = eh.containsSensitiveData(err.Error()) || eh.containsSensitiveData(message) // Update metrics eh.updateMetrics(secureErr) // Log error appropriately eh.logError(secureErr) return secureErr } // WrapErrorWithContext wraps an error with additional context func (eh *ErrorHandler) WrapErrorWithContext(ctx context.Context, err error, code string, message string, category ErrorCategory, severity ErrorSeverity, context map[string]interface{}) *SecureError { secureErr := eh.WrapError(err, code, message, category, severity) if secureErr == nil { return nil } // Add context while sanitizing sensitive data for key, value := range context { if !eh.isSensitiveField(key) { secureErr.Context[key] = value } else { secureErr.Context[key] = "[REDACTED]" secureErr.Sensitive = true } } // Add request context if available if ctx != nil { if requestID := ctx.Value("request_id"); requestID != nil { secureErr.Context["request_id"] = requestID } if userID := ctx.Value("user_id"); userID != nil { secureErr.Context["user_id"] = userID } if sessionID := ctx.Value("session_id"); sessionID != nil { secureErr.Context["session_id"] = sessionID } } return secureErr } // Error implements the error interface func (se *SecureError) Error() string { if se.Sensitive { return fmt.Sprintf("[%s] %s (sensitive data redacted)", se.Code, se.Message) } return fmt.Sprintf("[%s] %s", se.Code, se.Message) } // Unwrap returns the wrapped error func (se *SecureError) Unwrap() error { return se.Wrapped } // SafeString returns a safe string representation without sensitive data func (se *SecureError) SafeString() string { if se.Sensitive { return fmt.Sprintf("Error: %s (details redacted for security)", se.Message) } return se.Error() } // DetailedString returns detailed error information for internal logging func (se *SecureError) DetailedString() string { var parts []string parts = append(parts, fmt.Sprintf("Code: %s", se.Code)) parts = append(parts, fmt.Sprintf("Message: %s", se.Message)) parts = append(parts, fmt.Sprintf("Category: %s", se.Category)) parts = append(parts, fmt.Sprintf("Severity: %s", se.Severity)) parts = append(parts, fmt.Sprintf("Timestamp: %s", se.Timestamp.Format(time.RFC3339))) if len(se.Context) > 0 { parts = append(parts, fmt.Sprintf("Context: %+v", se.Context)) } if se.Wrapped != nil { parts = append(parts, fmt.Sprintf("Wrapped: %s", se.Wrapped.Error())) } return strings.Join(parts, ", ") } // captureStackTrace captures the current call stack func (eh *ErrorHandler) captureStackTrace() []StackFrame { var frames []StackFrame // Skip the first few frames (this function and WrapError) for i := 3; i < 10; i++ { pc, file, line, ok := runtime.Caller(i) if !ok { break } fn := runtime.FuncForPC(pc) if fn == nil { continue } frames = append(frames, StackFrame{ Function: fn.Name(), File: file, Line: line, }) } return frames } // containsSensitiveData checks if the text contains sensitive information func (eh *ErrorHandler) containsSensitiveData(text string) bool { lowercaseText := strings.ToLower(text) for field := range eh.sensitiveFields { if strings.Contains(lowercaseText, field) { return true } } // Check for common patterns that might contain sensitive data sensitivePatterns := []string{ "0x[a-fA-F0-9]{40}", // Ethereum addresses "0x[a-fA-F0-9]{64}", // Private keys/hashes "\\b[A-Za-z0-9+/]{20,}={0,2}\\b", // Base64 encoded data } for _, pattern := range sensitivePatterns { if matched, _ := regexp.MatchString(pattern, text); matched { return true } } return false } // isSensitiveField checks if a field name indicates sensitive data func (eh *ErrorHandler) isSensitiveField(fieldName string) bool { return eh.sensitiveFields[strings.ToLower(fieldName)] } // updateMetrics updates error metrics func (eh *ErrorHandler) updateMetrics(err *SecureError) { eh.errorMetrics.TotalErrors++ eh.errorMetrics.ErrorsByCategory[err.Category]++ eh.errorMetrics.ErrorsBySeverity[err.Severity]++ if err.Sensitive { eh.errorMetrics.SensitiveDataLeaks++ } } // logError logs the error appropriately based on sensitivity and severity func (eh *ErrorHandler) logError(err *SecureError) { logContext := map[string]interface{}{ "error_code": err.Code, "error_category": string(err.Category), "error_severity": string(err.Severity), "timestamp": err.Timestamp, } // Add safe context for key, value := range err.Context { if !eh.isSensitiveField(key) { logContext[key] = value } } logMessage := err.Message if err.Sensitive { logMessage = "Sensitive error occurred (details redacted)" logContext["sensitive"] = true } switch err.Severity { case ErrorSeverityCritical: eh.logger.Error(logMessage) case ErrorSeverityHigh: eh.logger.Error(logMessage) case ErrorSeverityMedium: eh.logger.Warn(logMessage) case ErrorSeverityLow: eh.logger.Info(logMessage) default: eh.logger.Info(logMessage) } } // GetMetrics returns current error metrics func (eh *ErrorHandler) GetMetrics() *ErrorMetrics { return eh.errorMetrics } // Common error creation helpers // NewAuthenticationError creates a new authentication error func (eh *ErrorHandler) NewAuthenticationError(message string, err error) *SecureError { return eh.WrapError(err, "AUTH_FAILED", message, ErrorCategoryAuthentication, ErrorSeverityHigh) } // NewAuthorizationError creates a new authorization error func (eh *ErrorHandler) NewAuthorizationError(message string, err error) *SecureError { return eh.WrapError(err, "AUTHZ_FAILED", message, ErrorCategoryAuthorization, ErrorSeverityHigh) } // NewValidationError creates a new validation error func (eh *ErrorHandler) NewValidationError(message string, err error) *SecureError { return eh.WrapError(err, "VALIDATION_FAILED", message, ErrorCategoryValidation, ErrorSeverityMedium) } // NewRateLimitError creates a new rate limit error func (eh *ErrorHandler) NewRateLimitError(message string, err error) *SecureError { return eh.WrapError(err, "RATE_LIMIT_EXCEEDED", message, ErrorCategoryRateLimit, ErrorSeverityMedium) } // NewEncryptionError creates a new encryption error func (eh *ErrorHandler) NewEncryptionError(message string, err error) *SecureError { return eh.WrapError(err, "ENCRYPTION_FAILED", message, ErrorCategoryEncryption, ErrorSeverityCritical) } // NewTransactionError creates a new transaction error func (eh *ErrorHandler) NewTransactionError(message string, err error) *SecureError { return eh.WrapError(err, "TRANSACTION_FAILED", message, ErrorCategoryTransaction, ErrorSeverityHigh) } // NewInternalError creates a new internal error func (eh *ErrorHandler) NewInternalError(message string, err error) *SecureError { return eh.WrapError(err, "INTERNAL_ERROR", message, ErrorCategoryInternal, ErrorSeverityCritical) }