agent_sandbox/fs/
capability.rs1use std::path::{Path, PathBuf};
2
3use crate::error::{Result, SandboxError};
4
5pub fn validate_path(root: &Path, requested: &str) -> Result<PathBuf> {
8 let root = root.canonicalize().map_err(SandboxError::Io)?;
9
10 let full_path = root.join(requested);
11
12 let resolved = if full_path.exists() {
14 full_path.canonicalize().map_err(SandboxError::Io)?
15 } else {
16 normalize_path(&full_path)
18 };
19
20 if !resolved.starts_with(&root) {
21 return Err(SandboxError::PathTraversal(format!(
22 "'{}' escapes sandbox root '{}'",
23 requested,
24 root.display()
25 )));
26 }
27
28 Ok(resolved)
29}
30
31fn normalize_path(path: &Path) -> PathBuf {
33 let mut result = PathBuf::new();
34
35 for component in path.components() {
36 match component {
37 std::path::Component::ParentDir => {
38 result.pop();
39 }
40 std::path::Component::CurDir => {}
41 other => result.push(other),
42 }
43 }
44
45 result
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_valid_path() {
54 let tmp = tempfile::tempdir().unwrap();
55 let root = tmp.path();
56
57 std::fs::write(root.join("test.txt"), "hello").unwrap();
59
60 let result = validate_path(root, "test.txt");
61 assert!(result.is_ok());
62 }
63
64 #[test]
65 fn test_path_traversal_blocked() {
66 let tmp = tempfile::tempdir().unwrap();
67 let root = tmp.path();
68
69 let result = validate_path(root, "../../../etc/passwd");
70 assert!(result.is_err());
71 assert!(matches!(
72 result.unwrap_err(),
73 SandboxError::PathTraversal(_)
74 ));
75 }
76
77 #[test]
78 fn test_nested_path() {
79 let tmp = tempfile::tempdir().unwrap();
80 let root = tmp.path();
81 std::fs::create_dir_all(root.join("a/b")).unwrap();
82 std::fs::write(root.join("a/b/c.txt"), "content").unwrap();
83
84 let result = validate_path(root, "a/b/c.txt");
85 assert!(result.is_ok());
86 }
87
88 #[test]
89 fn test_nonexistent_path_within_root() {
90 let tmp = tempfile::tempdir().unwrap();
91 let root = tmp.path();
92
93 let result = validate_path(root, "new_file.txt");
94 assert!(result.is_ok());
95 }
96}