diff --git a/src/cortex-execpolicy/src/detection.rs b/src/cortex-execpolicy/src/detection.rs index 3c8b8d854..2da6c0c6a 100644 --- a/src/cortex-execpolicy/src/detection.rs +++ b/src/cortex-execpolicy/src/detection.rs @@ -17,20 +17,20 @@ impl<'a> DetectionHelper<'a> { /// Normalize a path for comparison. pub fn normalize_path(path: &str) -> String { - let mut normalized = path.replace("//", "/"); + let mut normalized = path.replace('\\', "/"); - // Handle trailing slashes - while normalized.len() > 1 && normalized.ends_with('/') { - normalized.pop(); - } - - // Expand ~ to /home or user directory indicator if normalized.starts_with('~') { normalized = normalized.replacen('~', "/home", 1); } - // Handle Windows paths - normalized = normalized.replace('\\', "/"); + while normalized.contains("//") { + normalized = normalized.replace("//", "/"); + } + + // Handle trailing slashes + while normalized.len() > 1 && normalized.ends_with('/') { + normalized.pop(); + } normalized } diff --git a/src/cortex-execpolicy/src/tests.rs b/src/cortex-execpolicy/src/tests.rs index df773d2d6..d09e03273 100644 --- a/src/cortex-execpolicy/src/tests.rs +++ b/src/cortex-execpolicy/src/tests.rs @@ -72,6 +72,22 @@ mod decision_tests { mod parsed_command_tests { use super::*; + #[test] + fn test_normalize_path_collapses_repeated_slashes() { + assert_eq!( + crate::detection::DetectionHelper::normalize_path("///etc/shadow"), + "/etc/shadow" + ); + assert_eq!( + crate::detection::DetectionHelper::normalize_path("////etc//passwd///"), + "/etc/passwd" + ); + assert_eq!( + crate::detection::DetectionHelper::normalize_path(r"C:\\Users\\secrets\\"), + "C:/Users/secrets" + ); + } + #[test] fn test_parse_simple_command() { let cmd = ParsedCommand::from_args(&["ls".to_string(), "-la".to_string()]).unwrap(); @@ -254,6 +270,28 @@ mod destructive_file_ops_tests { } } + #[test] + fn test_multi_slash_sensitive_paths_denied() { + let policy = ExecPolicy::new(); + + let bypass_attempts = vec![ + vec!["rm", "-rf", "///etc"], + vec!["rm", "-rf", "////etc/passwd"], + vec!["shred", "///etc/shadow"], + vec!["chmod", "777", "///etc/passwd"], + ]; + + for cmd in bypass_attempts { + let cmd_strings: Vec = cmd.iter().map(|s| s.to_string()).collect(); + assert_eq!( + policy.evaluate(&cmd_strings), + Decision::Deny, + "Failed for: {:?}", + cmd + ); + } + } + #[test] fn test_rm_on_safe_path_allowed() { let policy = ExecPolicy::new();