An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
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}