Skip to main content

a3s_code_core/security/
config.rs

1//! Security Configuration
2//!
3//! Security Configuration
4//!
5//! Re-exports shared privacy types from `a3s-common` and defines the per-session
6//! SecurityConfig for security settings within a3s-code.
7
8// Re-export from shared a3s-common crate (single source of truth)
9pub use a3s_common::privacy::ClassificationRule;
10pub use a3s_common::privacy::RedactionStrategy;
11pub use a3s_common::privacy::SensitivityLevel;
12pub use a3s_common::privacy::{default_classification_rules, default_dangerous_commands};
13
14/// Feature toggles for individual Security components
15#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
16pub struct FeatureToggles {
17    /// Enable output sanitization
18    pub output_sanitizer: bool,
19    /// Enable taint tracking
20    pub taint_tracking: bool,
21    /// Enable tool interception
22    pub tool_interceptor: bool,
23    /// Enable prompt injection detection
24    pub injection_defense: bool,
25}
26
27impl Default for FeatureToggles {
28    fn default() -> Self {
29        Self {
30            output_sanitizer: true,
31            taint_tracking: true,
32            tool_interceptor: true,
33            injection_defense: true,
34        }
35    }
36}
37
38/// Main Security configuration for a session
39#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
40pub struct SecurityConfig {
41    /// Whether Security security is enabled
42    pub enabled: bool,
43    /// Classification rules for detecting sensitive data
44    pub classification_rules: Vec<ClassificationRule>,
45    /// How to redact detected sensitive data
46    pub redaction_strategy: RedactionStrategy,
47    /// Allowed network destinations (for tool interception)
48    pub network_whitelist: Vec<String>,
49    /// Dangerous command patterns (regex) to block
50    pub dangerous_commands: Vec<String>,
51    /// Feature toggles for individual components
52    pub features: FeatureToggles,
53}
54
55impl Default for SecurityConfig {
56    fn default() -> Self {
57        Self {
58            enabled: true,
59            classification_rules: default_classification_rules(),
60            redaction_strategy: RedactionStrategy::Remove,
61            network_whitelist: Vec::new(),
62            dangerous_commands: default_dangerous_commands(),
63            features: FeatureToggles::default(),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_sensitivity_level_ordering() {
74        assert!(SensitivityLevel::Public < SensitivityLevel::Normal);
75        assert!(SensitivityLevel::Normal < SensitivityLevel::Sensitive);
76        assert!(SensitivityLevel::Sensitive < SensitivityLevel::HighlySensitive);
77    }
78
79    #[test]
80    fn test_sensitivity_level_display() {
81        assert_eq!(SensitivityLevel::Public.to_string(), "Public");
82        assert_eq!(
83            SensitivityLevel::HighlySensitive.to_string(),
84            "HighlySensitive"
85        );
86    }
87
88    #[test]
89    fn test_default_config() {
90        let config = SecurityConfig::default();
91        assert!(config.enabled);
92        assert!(!config.classification_rules.is_empty());
93        assert_eq!(config.redaction_strategy, RedactionStrategy::Remove);
94        assert!(!config.dangerous_commands.is_empty());
95        assert!(config.features.output_sanitizer);
96    }
97
98    #[test]
99    fn test_default_classification_rules() {
100        let rules = default_classification_rules();
101        assert_eq!(rules.len(), 5);
102
103        let names: Vec<&str> = rules.iter().map(|r| r.name.as_str()).collect();
104        assert!(names.contains(&"credit_card"));
105        assert!(names.contains(&"ssn"));
106        assert!(names.contains(&"email"));
107        assert!(names.contains(&"phone"));
108        assert!(names.contains(&"api_key"));
109    }
110
111    #[test]
112    fn test_default_dangerous_commands() {
113        let commands = default_dangerous_commands();
114        assert!(!commands.is_empty());
115        // Should contain destructive command patterns
116        assert!(commands.iter().any(|c| c.contains("rm")));
117        assert!(commands.iter().any(|c| c.contains("dd")));
118    }
119
120    #[test]
121    fn test_config_serialization_roundtrip() {
122        let config = SecurityConfig::default();
123        let json = serde_json::to_string(&config).unwrap();
124        let parsed: SecurityConfig = serde_json::from_str(&json).unwrap();
125        assert_eq!(parsed.enabled, config.enabled);
126        assert_eq!(parsed.redaction_strategy, config.redaction_strategy);
127        assert_eq!(
128            parsed.classification_rules.len(),
129            config.classification_rules.len()
130        );
131    }
132
133    #[test]
134    fn test_redaction_strategy_serialization() {
135        let mask = RedactionStrategy::Mask;
136        let json = serde_json::to_string(&mask).unwrap();
137        assert_eq!(json, "\"Mask\"");
138
139        let remove = RedactionStrategy::Remove;
140        let json = serde_json::to_string(&remove).unwrap();
141        assert_eq!(json, "\"Remove\"");
142
143        let hash = RedactionStrategy::Hash;
144        let json = serde_json::to_string(&hash).unwrap();
145        assert_eq!(json, "\"Hash\"");
146    }
147}