use security_core::severity::SecuritySeverity;
mod private {
pub trait Sealed {}
}
pub trait SecurityIncident: private::Sealed {
fn incident_fingerprint(&self) -> &'static str;
fn alert_severity(&self) -> SecuritySeverity;
fn security_signal(&self) -> bool;
}
use crate::kind::AppError;
impl private::Sealed for AppError {}
impl SecurityIncident for AppError {
fn incident_fingerprint(&self) -> &'static str {
match self {
AppError::Validation { .. } => "validation_error",
AppError::Forbidden { .. } => "access_denied",
AppError::NotFound => "not_found",
AppError::Conflict => "conflict",
AppError::Dependency { .. } => "dependency_failure",
AppError::Crypto => "crypto_failure",
AppError::Internal => "internal_error",
AppError::RateLimit { .. } => "rate_limit_exceeded",
}
}
fn alert_severity(&self) -> SecuritySeverity {
match self {
AppError::Forbidden { .. } | AppError::Crypto => SecuritySeverity::High,
AppError::Dependency { .. } | AppError::Internal => SecuritySeverity::Medium,
AppError::RateLimit { .. } => SecuritySeverity::Low,
_ => SecuritySeverity::Info,
}
}
fn security_signal(&self) -> bool {
matches!(self, AppError::Forbidden { .. } | AppError::Crypto)
}
}
pub fn emit_event_for_incident(error: &AppError) {
use security_events::emit::emit_security_event;
use security_events::event::{EventOutcome, SecurityEvent};
use security_events::kind::EventKind;
let maybe_event = match error {
AppError::Forbidden { .. } => Some(SecurityEvent::new(
EventKind::AuthzDeny,
error.alert_severity(),
EventOutcome::Blocked,
)),
AppError::Dependency { .. } => Some(SecurityEvent::new(
EventKind::ErrorEscalation,
error.alert_severity(),
EventOutcome::Failure,
)),
_ => None,
};
if let Some(event) = maybe_event {
emit_security_event(event);
}
}