llm_config_security/
errors.rs

1//! Security error types
2
3use thiserror::Error;
4
5/// Security operation result type
6pub type SecurityResult<T> = Result<T, SecurityError>;
7
8/// Security errors
9#[derive(Error, Debug)]
10pub enum SecurityError {
11    /// Input validation failed
12    #[error("Input validation failed: {0}")]
13    ValidationError(String),
14
15    /// Rate limit exceeded
16    #[error("Rate limit exceeded: {0}")]
17    RateLimitExceeded(String),
18
19    /// Cryptographic operation failed
20    #[error("Cryptographic operation failed: {0}")]
21    CryptoError(String),
22
23    /// Policy violation
24    #[error("Security policy violation: {0}")]
25    PolicyViolation(String),
26
27    /// Authentication failed
28    #[error("Authentication failed: {0}")]
29    AuthenticationError(String),
30
31    /// Authorization failed
32    #[error("Authorization failed: {0}")]
33    AuthorizationError(String),
34
35    /// Invalid session
36    #[error("Invalid session: {0}")]
37    InvalidSession(String),
38
39    /// Suspicious activity detected
40    #[error("Suspicious activity detected: {0}")]
41    SuspiciousActivity(String),
42
43    /// Configuration error
44    #[error("Security configuration error: {0}")]
45    ConfigError(String),
46
47    /// Audit log validation failed
48    #[error("Audit log validation failed: {0}")]
49    AuditError(String),
50
51    /// SQL injection attempt detected
52    #[error("SQL injection attempt detected")]
53    SqlInjectionAttempt,
54
55    /// XSS attempt detected
56    #[error("XSS attempt detected")]
57    XssAttempt,
58
59    /// Path traversal attempt detected
60    #[error("Path traversal attempt detected")]
61    PathTraversalAttempt,
62
63    /// Command injection attempt detected
64    #[error("Command injection attempt detected")]
65    CommandInjectionAttempt,
66
67    /// LDAP injection attempt detected
68    #[error("LDAP injection attempt detected")]
69    LdapInjectionAttempt,
70
71    /// Regular expression DoS attempt
72    #[error("Regular expression DoS attempt detected")]
73    RegexDosAttempt,
74
75    /// Request too large
76    #[error("Request size exceeds maximum allowed: {0} bytes")]
77    RequestTooLarge(usize),
78
79    /// Invalid content type
80    #[error("Invalid content type: {0}")]
81    InvalidContentType(String),
82
83    /// Weak password
84    #[error("Password does not meet security requirements: {0}")]
85    WeakPassword(String),
86
87    /// Invalid token
88    #[error("Invalid or expired token")]
89    InvalidToken,
90
91    /// CSRF token mismatch
92    #[error("CSRF token mismatch")]
93    CsrfTokenMismatch,
94
95    /// Insecure protocol
96    #[error("Insecure protocol: {0}")]
97    InsecureProtocol(String),
98
99    /// Certificate validation failed
100    #[error("Certificate validation failed: {0}")]
101    CertificateError(String),
102
103    /// General error
104    #[error("Security error: {0}")]
105    General(String),
106}
107
108impl SecurityError {
109    /// Get the severity level of the error
110    pub fn severity(&self) -> Severity {
111        match self {
112            Self::SqlInjectionAttempt
113            | Self::XssAttempt
114            | Self::CommandInjectionAttempt
115            | Self::PathTraversalAttempt
116            | Self::SuspiciousActivity(_)
117            | Self::AuthenticationError(_)
118            | Self::AuthorizationError(_) => Severity::Critical,
119
120            Self::RateLimitExceeded(_)
121            | Self::PolicyViolation(_)
122            | Self::WeakPassword(_)
123            | Self::InvalidToken
124            | Self::CsrfTokenMismatch => Severity::High,
125
126            Self::ValidationError(_)
127            | Self::RequestTooLarge(_)
128            | Self::InvalidContentType(_)
129            | Self::InvalidSession(_) => Severity::Medium,
130
131            Self::ConfigError(_)
132            | Self::AuditError(_)
133            | Self::General(_) => Severity::Low,
134
135            Self::CryptoError(_)
136            | Self::CertificateError(_)
137            | Self::InsecureProtocol(_) => Severity::High,
138
139            Self::LdapInjectionAttempt | Self::RegexDosAttempt => Severity::Critical,
140        }
141    }
142
143    /// Check if the error should trigger an alert
144    pub fn should_alert(&self) -> bool {
145        matches!(self.severity(), Severity::Critical | Severity::High)
146    }
147
148    /// Get a sanitized error message for external display
149    pub fn public_message(&self) -> String {
150        match self {
151            // Don't expose internal details for security errors
152            Self::SqlInjectionAttempt
153            | Self::XssAttempt
154            | Self::CommandInjectionAttempt
155            | Self::PathTraversalAttempt
156            | Self::LdapInjectionAttempt
157            | Self::RegexDosAttempt
158            | Self::SuspiciousActivity(_) => {
159                "Request rejected due to security policy".to_string()
160            }
161
162            Self::RateLimitExceeded(_) => "Rate limit exceeded. Please try again later".to_string(),
163
164            Self::AuthenticationError(_) => "Authentication failed".to_string(),
165
166            Self::AuthorizationError(_) => "Access denied".to_string(),
167
168            Self::InvalidSession(_) => "Session expired. Please log in again".to_string(),
169
170            Self::WeakPassword(_) => {
171                "Password does not meet security requirements".to_string()
172            }
173
174            Self::RequestTooLarge(_) => "Request size too large".to_string(),
175
176            // For less sensitive errors, use the actual message
177            _ => self.to_string(),
178        }
179    }
180}
181
182/// Severity levels for security errors
183#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
184pub enum Severity {
185    Low,
186    Medium,
187    High,
188    Critical,
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_error_severity() {
197        assert_eq!(
198            SecurityError::SqlInjectionAttempt.severity(),
199            Severity::Critical
200        );
201        assert_eq!(
202            SecurityError::RateLimitExceeded("test".to_string()).severity(),
203            Severity::High
204        );
205        assert_eq!(
206            SecurityError::ValidationError("test".to_string()).severity(),
207            Severity::Medium
208        );
209    }
210
211    #[test]
212    fn test_should_alert() {
213        assert!(SecurityError::SqlInjectionAttempt.should_alert());
214        assert!(SecurityError::WeakPassword("test".to_string()).should_alert());
215        assert!(!SecurityError::ValidationError("test".to_string()).should_alert());
216    }
217
218    #[test]
219    fn test_public_message() {
220        let err = SecurityError::SqlInjectionAttempt;
221        assert_eq!(
222            err.public_message(),
223            "Request rejected due to security policy"
224        );
225
226        let err = SecurityError::ValidationError("internal details".to_string());
227        assert!(err.public_message().contains("validation"));
228    }
229}