a3s_code_core/security/
config.rs1pub 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
16pub struct FeatureToggles {
17 pub output_sanitizer: bool,
19 pub taint_tracking: bool,
21 pub tool_interceptor: bool,
23 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
40pub struct SecurityConfig {
41 pub enabled: bool,
43 pub classification_rules: Vec<ClassificationRule>,
45 pub redaction_strategy: RedactionStrategy,
47 pub network_whitelist: Vec<String>,
49 pub dangerous_commands: Vec<String>,
51 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 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}