use crate::policy::LeakPolicy; use crate::report::{EnrichedEvent, LeakTransport, LeakType, Severity}; #[derive(Debug, Clone)] pub struct LeakDecision { pub leak_type: LeakType, pub severity: Severity, pub policy_rule_id: String, } pub fn evaluate(event: &EnrichedEvent, policy: &LeakPolicy) -> Option { match event.transport { LeakTransport::Udp53 | LeakTransport::Tcp53 => { if is_proxy_required(event, policy) && !is_allowed(event, policy) { return Some(LeakDecision { leak_type: LeakType::B, severity: Severity::P1, policy_rule_id: "LEAK_B_PROXY_REQUIRED".to_string(), }); } if !is_allowed(event, policy) { return Some(LeakDecision { leak_type: LeakType::A, severity: Severity::P0, policy_rule_id: "LEAK_A_PLAINTEXT".to_string(), }); } } LeakTransport::Dot | LeakTransport::Doh => { if !is_allowed(event, policy) { return Some(LeakDecision { leak_type: LeakType::C, severity: Severity::P1, policy_rule_id: "LEAK_C_ENCRYPTED".to_string(), }); } } LeakTransport::Unknown => {} } None } fn is_allowed(event: &EnrichedEvent, policy: &LeakPolicy) -> bool { let has_rules = !policy.allowed_ifaces.is_empty() || !policy.allowed_destinations.is_empty() || !policy.allowed_ports.is_empty() || !policy.allowed_processes.is_empty(); if !has_rules { return false; } if let Some(iface) = event.iface_name.as_ref() { if policy .allowed_ifaces .iter() .any(|allowed| allowed.eq_ignore_ascii_case(iface)) { return true; } } if policy .allowed_ports .iter() .any(|port| *port == event.dst_port) { return true; } if policy .allowed_destinations .iter() .any(|net| net.contains(&event.dst_ip)) { return true; } if let Some(name) = event .owner .as_ref() .and_then(|owner| owner.process_name.as_ref()) { if policy .allowed_processes .iter() .any(|value| value.eq_ignore_ascii_case(name)) { return true; } } false } fn is_proxy_required(event: &EnrichedEvent, policy: &LeakPolicy) -> bool { let Some(qname) = event.qname.as_ref() else { return false; }; let qname = qname.to_ascii_lowercase(); if policy.proxy_required_domains.iter().any(|domain| { let domain = domain.to_ascii_lowercase(); qname == domain || qname.ends_with(&format!(".{domain}")) }) { return true; } if !policy.allowlist_domains.is_empty() { let allowed = policy.allowlist_domains.iter().any(|domain| { let domain = domain.to_ascii_lowercase(); qname == domain || qname.ends_with(&format!(".{domain}")) }); return !allowed; } false }