An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at main 221 lines 6.7 kB view raw
1use rustagent::config::{SecurityConfig, ShellPolicy}; 2use rustagent::security::{SecurityValidator, ValidationResult}; 3use std::path::PathBuf; 4use tempfile::TempDir; 5 6#[test] 7fn test_allowlist_policy() { 8 let config = SecurityConfig { 9 shell_policy: ShellPolicy::Allowlist, 10 allowed_commands: vec!["git".to_string(), "cargo".to_string()], 11 blocked_patterns: vec![], 12 max_file_size_mb: 10, 13 allowed_paths: vec![".".to_string()], 14 }; 15 16 let validator = SecurityValidator::new(config).unwrap(); 17 18 // Allowed command 19 match validator.validate_shell_command("git status") { 20 ValidationResult::Allowed => {} 21 _ => panic!("Expected allowed"), 22 } 23 24 // Not in allowlist 25 match validator.validate_shell_command("rm file") { 26 ValidationResult::RequiresPermission(_) => {} 27 _ => panic!("Expected permission required"), 28 } 29} 30 31#[test] 32fn test_blocklist_policy() { 33 let config = SecurityConfig { 34 shell_policy: ShellPolicy::Blocklist, 35 allowed_commands: vec![], 36 blocked_patterns: vec!["rm -rf".to_string(), "eval.*".to_string()], 37 max_file_size_mb: 10, 38 allowed_paths: vec![".".to_string()], 39 }; 40 41 let validator = SecurityValidator::new(config).unwrap(); 42 43 // Safe command 44 match validator.validate_shell_command("ls -la") { 45 ValidationResult::Allowed => {} 46 _ => panic!("Expected allowed"), 47 } 48 49 // Blocked pattern 50 match validator.validate_shell_command("rm -rf /") { 51 ValidationResult::Denied(_) => {} 52 _ => panic!("Expected denied"), 53 } 54} 55 56#[test] 57fn test_path_validation() { 58 let config = SecurityConfig { 59 shell_policy: ShellPolicy::Allowlist, 60 allowed_commands: vec![], 61 blocked_patterns: vec![], 62 max_file_size_mb: 10, 63 allowed_paths: vec![".".to_string()], 64 }; 65 66 let validator = SecurityValidator::new(config).unwrap(); 67 68 // Path in current directory 69 match validator.validate_file_path(&PathBuf::from("./test.txt")) { 70 ValidationResult::Allowed => {} 71 _ => panic!("Expected allowed"), 72 } 73} 74 75#[test] 76fn test_nested_nonexistent_dirs() { 77 let temp_dir = TempDir::new().unwrap(); 78 let config = SecurityConfig { 79 shell_policy: ShellPolicy::Allowlist, 80 allowed_commands: vec![], 81 blocked_patterns: vec![], 82 max_file_size_mb: 10, 83 allowed_paths: vec![temp_dir.path().to_string_lossy().to_string()], 84 }; 85 86 let validator = SecurityValidator::new(config).unwrap(); 87 88 let nested_path = temp_dir.path().join("new").join("sub").join("file.txt"); 89 match validator.validate_file_path(&nested_path) { 90 ValidationResult::Allowed => {} 91 result => panic!("Expected allowed, got {:?}", result), 92 } 93} 94 95#[test] 96fn test_traversal_in_suffix_denied() { 97 let temp_dir = TempDir::new().unwrap(); 98 let config = SecurityConfig { 99 shell_policy: ShellPolicy::Allowlist, 100 allowed_commands: vec![], 101 blocked_patterns: vec![], 102 max_file_size_mb: 10, 103 allowed_paths: vec![temp_dir.path().to_string_lossy().to_string()], 104 }; 105 106 let validator = SecurityValidator::new(config).unwrap(); 107 108 let traversal_path = temp_dir 109 .path() 110 .join("new") 111 .join("..") 112 .join("..") 113 .join("etc") 114 .join("passwd"); 115 match validator.validate_file_path(&traversal_path) { 116 ValidationResult::Denied(msg) => { 117 assert!( 118 msg.contains("traversal"), 119 "Expected traversal error, got: {}", 120 msg 121 ); 122 } 123 result => panic!("Expected denied, got {:?}", result), 124 } 125} 126 127#[test] 128fn test_dot_component_normalized() { 129 let temp_dir = TempDir::new().unwrap(); 130 let config = SecurityConfig { 131 shell_policy: ShellPolicy::Allowlist, 132 allowed_commands: vec![], 133 blocked_patterns: vec![], 134 max_file_size_mb: 10, 135 allowed_paths: vec![temp_dir.path().to_string_lossy().to_string()], 136 }; 137 138 let validator = SecurityValidator::new(config).unwrap(); 139 140 let dot_path = temp_dir 141 .path() 142 .join("new") 143 .join(".") 144 .join("sub") 145 .join("file.txt"); 146 match validator.validate_file_path(&dot_path) { 147 ValidationResult::Allowed => {} 148 result => panic!("Expected allowed (dot normalized), got {:?}", result), 149 } 150} 151 152#[test] 153fn test_existing_path_no_regression() { 154 let temp_dir = TempDir::new().unwrap(); 155 let existing_file = temp_dir.path().join("existing.txt"); 156 std::fs::write(&existing_file, "test").unwrap(); 157 158 let config = SecurityConfig { 159 shell_policy: ShellPolicy::Allowlist, 160 allowed_commands: vec![], 161 blocked_patterns: vec![], 162 max_file_size_mb: 10, 163 allowed_paths: vec![temp_dir.path().to_string_lossy().to_string()], 164 }; 165 166 let validator = SecurityValidator::new(config).unwrap(); 167 168 match validator.validate_file_path(&existing_file) { 169 ValidationResult::Allowed => {} 170 result => panic!("Expected allowed for existing file, got {:?}", result), 171 } 172} 173 174#[test] 175fn test_path_outside_allowed_requires_permission() { 176 let temp_dir = TempDir::new().unwrap(); 177 let config = SecurityConfig { 178 shell_policy: ShellPolicy::Allowlist, 179 allowed_commands: vec![], 180 blocked_patterns: vec![], 181 max_file_size_mb: 10, 182 allowed_paths: vec![temp_dir.path().to_string_lossy().to_string()], 183 }; 184 185 let validator = SecurityValidator::new(config).unwrap(); 186 187 let outside_path = PathBuf::from("/tmp/outside/file.txt"); 188 match validator.validate_file_path(&outside_path) { 189 ValidationResult::RequiresPermission(_) => {} 190 result => panic!("Expected requires permission, got {:?}", result), 191 } 192} 193 194#[test] 195fn test_traversal_via_existing_path() { 196 let temp_dir = TempDir::new().unwrap(); 197 std::fs::create_dir(temp_dir.path().join("subdir")).unwrap(); 198 199 let config = SecurityConfig { 200 shell_policy: ShellPolicy::Allowlist, 201 allowed_commands: vec![], 202 blocked_patterns: vec![], 203 max_file_size_mb: 10, 204 allowed_paths: vec![temp_dir.path().join("subdir").to_string_lossy().to_string()], 205 }; 206 207 let validator = SecurityValidator::new(config).unwrap(); 208 209 let traversal_path = temp_dir 210 .path() 211 .join("subdir") 212 .join("..") 213 .join("outside.txt"); 214 match validator.validate_file_path(&traversal_path) { 215 ValidationResult::RequiresPermission(_) => {} 216 result => panic!( 217 "Expected requires permission for path outside allowed dirs, got {:?}", 218 result 219 ), 220 } 221}