1use crate::crypto::{KeyStore, SoftwareKeyStore};
40use crate::proof::ActionType;
41use crate::watchdog::DenialProof;
42use parking_lot::RwLock;
43use serde::{Deserialize, Serialize};
44use sha2::{Digest, Sha256};
45use std::collections::HashMap;
46use std::sync::atomic::{AtomicU64, Ordering};
47use std::sync::Arc;
48use std::time::{SystemTime, UNIX_EPOCH};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub enum ThreatLevel {
57 Low,
59 Medium,
61 High,
63 Critical,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
69pub enum AttackCategory {
70 FileAccess,
72 CommandInjection,
74 PrivilegeEscalation,
76 DataExfiltration,
78 DenialOfService,
80 Replay,
82 TimingAttack,
84 Unknown,
86}
87
88impl AttackCategory {
89 pub fn from_code(code: u8) -> Self {
91 match code {
92 0 => AttackCategory::FileAccess,
93 1 => AttackCategory::CommandInjection,
94 2 => AttackCategory::PrivilegeEscalation,
95 3 => AttackCategory::DataExfiltration,
96 4 => AttackCategory::DenialOfService,
97 5 => AttackCategory::Replay,
98 6 => AttackCategory::TimingAttack,
99 _ => AttackCategory::Unknown,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct AttackPattern {
107 pub id: String,
109
110 pub signature: [u8; 32],
112
113 pub category: AttackCategory,
115
116 pub threat_level: ThreatLevel,
118
119 pub action_type: ActionType,
121
122 pub target_patterns: Vec<String>,
124
125 pub keywords: Vec<String>,
127
128 pub timing_signature: Option<TimingSignature>,
130
131 pub triggered_rule: String,
133
134 pub first_seen: u64,
136
137 pub occurrence_count: u64,
139
140 pub confidence: f64,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct TimingSignature {
147 pub avg_interval_ms: u64,
149 pub variance: f64,
151 pub is_automated: bool,
153}
154
155impl AttackPattern {
156 pub fn from_denial(denial: &DenialProof, _additional_context: Option<&str>) -> Self {
158 let now = SystemTime::now()
159 .duration_since(UNIX_EPOCH)
160 .unwrap()
161 .as_secs();
162
163 let keywords = Self::extract_keywords(&denial.denial_reason);
165
166 let category = Self::categorize(&denial.action_type, &keywords);
168
169 let threat_level = Self::assess_threat(&category, &keywords);
171
172 let target_patterns = Self::extract_targets(&denial.denial_reason);
174
175 let signature = Self::compute_signature(denial, &category, &keywords);
177
178 let id = format!("AP-{:08x}-{:04x}", now as u32, rand::random::<u16>());
180
181 AttackPattern {
182 id,
183 signature,
184 category,
185 threat_level,
186 action_type: denial.action_type.clone(),
187 target_patterns,
188 keywords,
189 timing_signature: None,
190 triggered_rule: denial.violated_rule.clone(),
191 first_seen: now,
192 occurrence_count: 1,
193 confidence: 0.8, }
195 }
196
197 fn extract_keywords(description: &str) -> Vec<String> {
199 let dangerous_keywords = [
200 "password",
201 "secret",
202 "key",
203 "token",
204 "credential",
205 "admin",
206 "root",
207 "sudo",
208 "exec",
209 "eval",
210 "system",
211 "delete",
212 "drop",
213 "truncate",
214 "rm",
215 "format",
216 "inject",
217 "bypass",
218 "escalate",
219 "overflow",
220 "/etc/passwd",
221 "/etc/shadow",
222 ".ssh",
223 ".env",
224 "curl",
225 "wget",
226 "nc",
227 "netcat",
228 "bash",
229 "sh",
230 ];
231
232 let lower = description.to_lowercase();
233 dangerous_keywords
234 .iter()
235 .filter(|kw| lower.contains(*kw))
236 .map(|s| s.to_string())
237 .collect()
238 }
239
240 fn categorize(action_type: &ActionType, keywords: &[String]) -> AttackCategory {
242 for kw in keywords {
244 match kw.as_str() {
245 "inject" | "exec" | "eval" | "system" | "bash" | "sh" => {
246 return AttackCategory::CommandInjection;
247 }
248 "admin" | "root" | "sudo" | "escalate" => {
249 return AttackCategory::PrivilegeEscalation;
250 }
251 "password" | "secret" | "key" | "token" | ".env" | ".ssh" => {
252 return AttackCategory::DataExfiltration;
253 }
254 _ => {}
255 }
256 }
257
258 match action_type {
260 ActionType::Read => AttackCategory::FileAccess,
261 ActionType::Write => AttackCategory::FileAccess,
262 ActionType::Delete => AttackCategory::DenialOfService,
263 ActionType::Execute => AttackCategory::CommandInjection,
264 ActionType::Network => AttackCategory::DataExfiltration,
265 ActionType::Custom(_) => AttackCategory::Unknown,
266 }
267 }
268
269 fn assess_threat(category: &AttackCategory, keywords: &[String]) -> ThreatLevel {
271 let critical_keywords = ["passwd", "shadow", "sudo", "root", "admin"];
273 let high_keywords = ["exec", "eval", "system", "inject", "bypass"];
274
275 for kw in keywords {
276 if critical_keywords.iter().any(|c| kw.contains(c)) {
277 return ThreatLevel::Critical;
278 }
279 if high_keywords.iter().any(|h| kw.contains(h)) {
280 return ThreatLevel::High;
281 }
282 }
283
284 match category {
285 AttackCategory::PrivilegeEscalation => ThreatLevel::Critical,
286 AttackCategory::CommandInjection => ThreatLevel::High,
287 AttackCategory::DataExfiltration => ThreatLevel::High,
288 AttackCategory::DenialOfService => ThreatLevel::Medium,
289 AttackCategory::FileAccess => ThreatLevel::Medium,
290 AttackCategory::Replay => ThreatLevel::Medium,
291 AttackCategory::TimingAttack => ThreatLevel::Low,
292 AttackCategory::Unknown => ThreatLevel::Medium,
293 }
294 }
295
296 fn extract_targets(description: &str) -> Vec<String> {
298 let mut targets = Vec::new();
299
300 for part in description.split_whitespace() {
302 if part.starts_with('/') || part.starts_with("./") || part.starts_with("..") {
303 targets.push(part.to_string());
304 }
305 if part.contains(".env") || part.contains(".ssh") || part.contains("passwd") {
306 targets.push(part.to_string());
307 }
308 }
309
310 targets
311 }
312
313 fn compute_signature(
315 denial: &DenialProof,
316 category: &AttackCategory,
317 keywords: &[String],
318 ) -> [u8; 32] {
319 let mut hasher = Sha256::new();
320
321 hasher.update([*category as u8]);
323
324 hasher.update(denial.action_hash);
326
327 let mut sorted_keywords = keywords.to_vec();
329 sorted_keywords.sort();
330 for kw in sorted_keywords {
331 hasher.update(kw.as_bytes());
332 }
333
334 hasher.finalize().into()
335 }
336
337 pub fn matches(&self, other: &AttackPattern) -> bool {
339 if self.signature == other.signature {
341 return true;
342 }
343
344 if self.category == other.category {
346 let overlap: usize = self
347 .keywords
348 .iter()
349 .filter(|k| other.keywords.contains(k))
350 .count();
351
352 if overlap >= 2 {
353 return true;
354 }
355 }
356
357 false
358 }
359}
360
361#[allow(dead_code)]
369pub struct ImmunityMemory {
370 patterns: RwLock<HashMap<[u8; 32], AttackPattern>>,
372
373 occurrences: RwLock<HashMap<[u8; 32], u64>>,
375
376 total_blocked: AtomicU64,
378
379 created_at: u64,
381
382 last_update: RwLock<u64>,
384}
385
386impl ImmunityMemory {
387 pub fn new() -> Self {
389 let now = SystemTime::now()
390 .duration_since(UNIX_EPOCH)
391 .unwrap()
392 .as_secs();
393
394 ImmunityMemory {
395 patterns: RwLock::new(HashMap::new()),
396 occurrences: RwLock::new(HashMap::new()),
397 total_blocked: AtomicU64::new(0),
398 created_at: now,
399 last_update: RwLock::new(now),
400 }
401 }
402
403 pub fn record(&self, pattern: AttackPattern) {
405 let signature = pattern.signature;
406
407 {
409 let mut occurrences = self.occurrences.write();
410 *occurrences.entry(signature).or_insert(0) += 1;
411 }
412
413 {
415 let mut patterns = self.patterns.write();
416 if let Some(existing) = patterns.get_mut(&signature) {
417 existing.occurrence_count += 1;
418 existing.confidence = (existing.confidence + 0.05).min(1.0);
420 } else {
421 patterns.insert(signature, pattern);
422 }
423 }
424
425 self.total_blocked.fetch_add(1, Ordering::SeqCst);
426
427 let now = SystemTime::now()
428 .duration_since(UNIX_EPOCH)
429 .unwrap()
430 .as_secs();
431 *self.last_update.write() = now;
432 }
433
434 pub fn is_known(&self, signature: &[u8; 32]) -> bool {
436 self.patterns.read().contains_key(signature)
437 }
438
439 pub fn get_pattern(&self, signature: &[u8; 32]) -> Option<AttackPattern> {
441 self.patterns.read().get(signature).cloned()
442 }
443
444 pub fn patterns_by_category(&self, category: AttackCategory) -> Vec<AttackPattern> {
446 self.patterns
447 .read()
448 .values()
449 .filter(|p| p.category == category)
450 .cloned()
451 .collect()
452 }
453
454 pub fn high_confidence_patterns(&self, min_confidence: f64) -> Vec<AttackPattern> {
456 self.patterns
457 .read()
458 .values()
459 .filter(|p| p.confidence >= min_confidence)
460 .cloned()
461 .collect()
462 }
463
464 pub fn pattern_count(&self) -> usize {
466 self.patterns.read().len()
467 }
468
469 pub fn total_blocked(&self) -> u64 {
471 self.total_blocked.load(Ordering::SeqCst)
472 }
473
474 pub fn threat_statistics(&self) -> HashMap<AttackCategory, u64> {
476 let mut stats = HashMap::new();
477
478 for pattern in self.patterns.read().values() {
479 *stats.entry(pattern.category).or_insert(0) += pattern.occurrence_count;
480 }
481
482 stats
483 }
484}
485
486impl Default for ImmunityMemory {
487 fn default() -> Self {
488 Self::new()
489 }
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct PolymorphicFilter {
499 pub id: String,
501
502 pub generation: u64,
504
505 pub rules: Vec<FilterRule>,
507
508 pub mutation_seed: u64,
510
511 pub created_at: u64,
513
514 pub parent_id: Option<String>,
516
517 pub wasm_bytecode: Option<Vec<u8>>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct FilterRule {
524 pub id: String,
526
527 pub category: AttackCategory,
529
530 pub keyword_patterns: Vec<String>,
532
533 pub regex_patterns: Vec<String>,
535
536 pub path_patterns: Vec<String>,
538
539 pub blocked_actions: Vec<ActionType>,
541
542 pub confidence_threshold: f64,
544}
545
546impl PolymorphicFilter {
547 pub fn from_patterns(patterns: &[AttackPattern]) -> Self {
549 let now = SystemTime::now()
550 .duration_since(UNIX_EPOCH)
551 .unwrap()
552 .as_secs();
553
554 let id = format!("PF-{:08x}-{:04x}", now as u32, rand::random::<u16>());
555
556 let rules: Vec<FilterRule> = patterns
557 .iter()
558 .map(|p| FilterRule {
559 id: format!("R-{}", &p.id[3..]),
560 category: p.category,
561 keyword_patterns: p.keywords.clone(),
562 regex_patterns: Vec::new(),
563 path_patterns: p.target_patterns.clone(),
564 blocked_actions: vec![p.action_type.clone()],
565 confidence_threshold: p.confidence,
566 })
567 .collect();
568
569 PolymorphicFilter {
570 id,
571 generation: 0,
572 rules,
573 mutation_seed: rand::random(),
574 created_at: now,
575 parent_id: None,
576 wasm_bytecode: None,
577 }
578 }
579
580 pub fn should_block(&self, action_type: &ActionType, description: &str) -> Option<&FilterRule> {
582 let lower_desc = description.to_lowercase();
583
584 for rule in &self.rules {
585 if !rule.blocked_actions.contains(action_type) {
587 continue;
588 }
589
590 for keyword in &rule.keyword_patterns {
592 if lower_desc.contains(&keyword.to_lowercase()) {
593 return Some(rule);
594 }
595 }
596
597 for path in &rule.path_patterns {
599 if description.contains(path) {
600 return Some(rule);
601 }
602 }
603 }
604
605 None
606 }
607
608 pub fn rule_count(&self) -> usize {
610 self.rules.len()
611 }
612}
613
614#[allow(dead_code)]
623pub struct MutationEngine {
624 mutation_count: AtomicU64,
626
627 seed: u64,
629}
630
631impl MutationEngine {
632 pub fn new() -> Self {
634 MutationEngine {
635 mutation_count: AtomicU64::new(0),
636 seed: rand::random(),
637 }
638 }
639
640 pub fn mutate(&self, filter: &PolymorphicFilter) -> PolymorphicFilter {
644 let mutation_id = self.mutation_count.fetch_add(1, Ordering::SeqCst);
645
646 let now = SystemTime::now()
647 .duration_since(UNIX_EPOCH)
648 .unwrap()
649 .as_secs();
650
651 let new_id = format!(
652 "PF-{:08x}-G{}-{:04x}",
653 now as u32,
654 filter.generation + 1,
655 rand::random::<u16>()
656 );
657
658 let mutated_rules: Vec<FilterRule> = filter
660 .rules
661 .iter()
662 .map(|r| self.mutate_rule(r, mutation_id))
663 .collect();
664
665 PolymorphicFilter {
666 id: new_id,
667 generation: filter.generation + 1,
668 rules: mutated_rules,
669 mutation_seed: rand::random(),
670 created_at: now,
671 parent_id: Some(filter.id.clone()),
672 wasm_bytecode: None, }
674 }
675
676 fn mutate_rule(&self, rule: &FilterRule, mutation_id: u64) -> FilterRule {
678 let mutated_keywords: Vec<String> = rule
680 .keyword_patterns
681 .iter()
682 .map(|kw| self.mutate_keyword(kw, mutation_id))
683 .collect();
684
685 FilterRule {
686 id: format!("{}-M{}", rule.id, mutation_id),
687 category: rule.category,
688 keyword_patterns: mutated_keywords,
689 regex_patterns: rule.regex_patterns.clone(),
690 path_patterns: rule.path_patterns.clone(),
691 blocked_actions: rule.blocked_actions.clone(),
692 confidence_threshold: rule.confidence_threshold,
693 }
694 }
695
696 fn mutate_keyword(&self, keyword: &str, _mutation_id: u64) -> String {
698 keyword.to_string()
702 }
703
704 pub fn mutation_count(&self) -> u64 {
706 self.mutation_count.load(Ordering::SeqCst)
707 }
708}
709
710impl Default for MutationEngine {
711 fn default() -> Self {
712 Self::new()
713 }
714}
715
716pub struct FilterGenerator {
722 keystore: SoftwareKeyStore,
724
725 filter_count: AtomicU64,
727}
728
729impl FilterGenerator {
730 pub fn new() -> Result<Self, crate::crypto::CryptoError> {
732 Ok(FilterGenerator {
733 keystore: SoftwareKeyStore::generate()?,
734 filter_count: AtomicU64::new(0),
735 })
736 }
737
738 pub fn generate(&self, patterns: &[AttackPattern]) -> SignedFilter {
740 let filter = PolymorphicFilter::from_patterns(patterns);
741 self.filter_count.fetch_add(1, Ordering::SeqCst);
742
743 let filter_bytes = serde_json::to_vec(&filter).unwrap_or_default();
745 let signature = self.keystore.sign(&filter_bytes).unwrap_or_default();
746
747 SignedFilter {
748 filter,
749 signature,
750 generator_pubkey: self.keystore.public_key_bytes(),
751 }
752 }
753
754 pub fn filter_count(&self) -> u64 {
756 self.filter_count.load(Ordering::SeqCst)
757 }
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize)]
762pub struct SignedFilter {
763 pub filter: PolymorphicFilter,
765 pub signature: Vec<u8>,
767 pub generator_pubkey: Vec<u8>,
769}
770
771impl SignedFilter {
772 pub fn verify(&self, keystore: &dyn KeyStore) -> bool {
774 let filter_bytes = serde_json::to_vec(&self.filter).unwrap_or_default();
775 keystore.verify(&filter_bytes, &self.signature).is_ok()
776 }
777}
778
779pub struct EvolutionaryGuard {
787 memory: Arc<ImmunityMemory>,
789
790 active_filters: RwLock<Vec<SignedFilter>>,
792
793 generator: FilterGenerator,
795
796 mutation_engine: MutationEngine,
798
799 id: String,
801
802 active: std::sync::atomic::AtomicBool,
804
805 generation: AtomicU64,
807
808 pending_broadcasts: RwLock<Vec<SignedFilter>>,
810}
811
812impl EvolutionaryGuard {
813 pub fn new() -> Result<Self, crate::crypto::CryptoError> {
815 let id = format!("EG-{:08x}", rand::random::<u32>());
816
817 Ok(EvolutionaryGuard {
818 memory: Arc::new(ImmunityMemory::new()),
819 active_filters: RwLock::new(Vec::new()),
820 generator: FilterGenerator::new()?,
821 mutation_engine: MutationEngine::new(),
822 id,
823 active: std::sync::atomic::AtomicBool::new(true),
824 generation: AtomicU64::new(0),
825 pending_broadcasts: RwLock::new(Vec::new()),
826 })
827 }
828
829 pub fn learn(&self, denial: &DenialProof) -> Option<SignedFilter> {
833 if !self.active.load(Ordering::SeqCst) {
834 return None;
835 }
836
837 let pattern = AttackPattern::from_denial(denial, None);
839
840 self.memory.record(pattern.clone());
842
843 let should_generate = match pattern.threat_level {
845 ThreatLevel::Critical => true,
846 ThreatLevel::High => pattern.occurrence_count >= 2,
847 ThreatLevel::Medium => pattern.occurrence_count >= 5,
848 ThreatLevel::Low => pattern.occurrence_count >= 10,
849 };
850
851 if should_generate {
852 let filter = self.generator.generate(&[pattern]);
854
855 self.active_filters.write().push(filter.clone());
857
858 self.pending_broadcasts.write().push(filter.clone());
860
861 self.generation.fetch_add(1, Ordering::SeqCst);
863
864 return Some(filter);
865 }
866
867 None
868 }
869
870 pub fn should_block(&self, action_type: &ActionType, description: &str) -> Option<String> {
872 for signed_filter in self.active_filters.read().iter() {
873 if let Some(rule) = signed_filter.filter.should_block(action_type, description) {
874 return Some(format!(
875 "Blocked by evolved filter {} (rule: {}, category: {:?})",
876 signed_filter.filter.id, rule.id, rule.category
877 ));
878 }
879 }
880 None
881 }
882
883 pub fn evolve(&self) {
885 let mut filters = self.active_filters.write();
886
887 let mutated: Vec<SignedFilter> = filters
888 .iter()
889 .map(|sf| {
890 let mutated_filter = self.mutation_engine.mutate(&sf.filter);
891 let filter_bytes = serde_json::to_vec(&mutated_filter).unwrap_or_default();
892 let signature = self
893 .generator
894 .keystore
895 .sign(&filter_bytes)
896 .unwrap_or_default();
897
898 SignedFilter {
899 filter: mutated_filter,
900 signature,
901 generator_pubkey: self.generator.keystore.public_key_bytes(),
902 }
903 })
904 .collect();
905
906 *filters = mutated;
907 self.generation.fetch_add(1, Ordering::SeqCst);
908 }
909
910 pub fn take_pending_broadcasts(&self) -> Vec<SignedFilter> {
912 std::mem::take(&mut *self.pending_broadcasts.write())
913 }
914
915 pub fn receive_filter(&self, filter: SignedFilter) {
917 self.active_filters.write().push(filter);
919 }
920
921 pub fn memory(&self) -> Arc<ImmunityMemory> {
923 self.memory.clone()
924 }
925
926 pub fn generation(&self) -> u64 {
928 self.generation.load(Ordering::SeqCst)
929 }
930
931 pub fn filter_count(&self) -> usize {
933 self.active_filters.read().len()
934 }
935
936 pub fn id(&self) -> &str {
938 &self.id
939 }
940
941 pub fn threat_stats(&self) -> HashMap<AttackCategory, u64> {
943 self.memory.threat_statistics()
944 }
945
946 pub fn broadcast_immunity(&self, sync: &crate::apex_protocol::SyncProtocol) -> usize {
959 if !self.active.load(Ordering::SeqCst) {
960 return 0;
961 }
962
963 let filters = self.take_pending_broadcasts();
965 let count = filters.len();
966
967 for filter in filters {
969 sync.broadcast_filter(filter);
970 }
971
972 let high_confidence = self.memory.high_confidence_patterns(0.9);
974 for pattern in high_confidence {
975 let fingerprint =
976 crate::apex_protocol::CompactedThreatFingerprint::from_pattern(&pattern, self.id());
977 sync.broadcast_threat(fingerprint);
978 }
979
980 count
981 }
982
983 pub fn sync_from_mesh(
987 &self,
988 fingerprints: &[crate::apex_protocol::CompactedThreatFingerprint],
989 ) {
990 for fingerprint in fingerprints {
991 let pattern = AttackPattern {
993 id: format!("FP-{:08x}", fingerprint.origin_hash as u32),
994 signature: fingerprint.signature,
995 category: AttackCategory::from_code(fingerprint.category_code),
996 threat_level: fingerprint.level,
997 action_type: crate::proof::ActionType::Custom("mesh-sync".to_string()),
998 target_patterns: Vec::new(),
999 keywords: Vec::new(),
1000 timing_signature: None,
1001 triggered_rule: "mesh-imported".to_string(),
1002 first_seen: fingerprint.timestamp,
1003 occurrence_count: 1,
1004 confidence: 0.7, };
1006
1007 self.memory.record(pattern);
1008 }
1009 }
1010
1011 pub fn deactivate(&self) {
1013 self.active.store(false, Ordering::SeqCst);
1014 }
1015}
1016
1017#[cfg(test)]
1022mod tests {
1023 use super::*;
1024 use crate::proof::Action;
1025 use crate::watchdog::DenialProof;
1026
1027 fn create_test_denial() -> DenialProof {
1028 let action = Action::read("/etc/passwd - trying to steal passwords");
1030
1031 DenialProof::new(
1032 &action,
1033 "No unauthorized file access".to_string(),
1034 "Attempted to read /etc/passwd - password file access blocked".to_string(),
1035 1,
1036 )
1037 }
1038
1039 #[test]
1040 fn test_attack_pattern_extraction() {
1041 let denial = create_test_denial();
1042 let pattern = AttackPattern::from_denial(&denial, None);
1043
1044 assert!(!pattern.id.is_empty());
1045 assert!(pattern.keywords.contains(&"password".to_string()));
1047 assert_eq!(pattern.threat_level, ThreatLevel::Critical);
1049 }
1050
1051 #[test]
1052 fn test_immunity_memory() {
1053 let memory = ImmunityMemory::new();
1054
1055 let denial = create_test_denial();
1056 let pattern = AttackPattern::from_denial(&denial, None);
1057 let signature = pattern.signature;
1058
1059 memory.record(pattern);
1060
1061 assert!(memory.is_known(&signature));
1062 assert_eq!(memory.pattern_count(), 1);
1063 assert_eq!(memory.total_blocked(), 1);
1064 }
1065
1066 #[test]
1067 fn test_filter_generation() {
1068 let denial = create_test_denial();
1069 let pattern = AttackPattern::from_denial(&denial, None);
1070
1071 let generator = FilterGenerator::new().unwrap();
1072 let signed_filter = generator.generate(&[pattern]);
1073
1074 assert!(!signed_filter.filter.rules.is_empty());
1075 assert!(!signed_filter.signature.is_empty());
1076 }
1077
1078 #[test]
1079 fn test_filter_blocks_similar_attack() {
1080 let denial = create_test_denial();
1081 let pattern = AttackPattern::from_denial(&denial, None);
1082
1083 let filter = PolymorphicFilter::from_patterns(&[pattern]);
1084
1085 let result = filter.should_block(&ActionType::Read, "/etc/passwd access attempt");
1087
1088 assert!(result.is_some());
1089 }
1090
1091 #[test]
1092 fn test_polymorphic_mutation() {
1093 let denial = create_test_denial();
1094 let pattern = AttackPattern::from_denial(&denial, None);
1095 let filter = PolymorphicFilter::from_patterns(&[pattern]);
1096
1097 let engine = MutationEngine::new();
1098 let mutated = engine.mutate(&filter);
1099
1100 assert_ne!(filter.id, mutated.id);
1101 assert_eq!(mutated.generation, filter.generation + 1);
1102 assert_eq!(mutated.parent_id, Some(filter.id));
1103 }
1104
1105 #[test]
1106 fn test_evolutionary_guard_learns() {
1107 let guard = EvolutionaryGuard::new().unwrap();
1108
1109 for _ in 0..3 {
1111 let denial = create_test_denial();
1112 guard.learn(&denial);
1113 }
1114
1115 assert!(guard.memory().pattern_count() >= 1);
1117 assert!(guard.memory().total_blocked() >= 3);
1118 }
1119
1120 #[test]
1121 fn test_system_learns_from_attack() {
1122 let guard = EvolutionaryGuard::new().unwrap();
1123
1124 let denial1 = create_test_denial();
1126 let result1 = guard.learn(&denial1);
1127
1128 assert!(result1.is_some());
1130
1131 let block_result = guard.should_block(
1133 &ActionType::Read,
1134 "Reading /etc/passwd for password harvesting",
1135 );
1136
1137 assert!(block_result.is_some());
1138 assert!(block_result.unwrap().contains("Blocked by evolved filter"));
1139 }
1140
1141 #[test]
1142 fn test_evolution_changes_filters() {
1143 let guard = EvolutionaryGuard::new().unwrap();
1144
1145 let denial = create_test_denial();
1147 guard.learn(&denial);
1148
1149 let gen_before = guard.generation();
1150
1151 guard.evolve();
1153
1154 let gen_after = guard.generation();
1155
1156 assert!(gen_after > gen_before);
1157 }
1158
1159 #[test]
1160 fn test_mesh_broadcast() {
1161 let guard = EvolutionaryGuard::new().unwrap();
1162
1163 let denial = create_test_denial();
1165 guard.learn(&denial);
1166
1167 let broadcasts = guard.take_pending_broadcasts();
1169 assert!(!broadcasts.is_empty());
1170
1171 let broadcasts2 = guard.take_pending_broadcasts();
1173 assert!(broadcasts2.is_empty());
1174 }
1175}