agent_code_lib/sandbox/
policy.rs1use std::path::{Path, PathBuf};
9
10use crate::config::SandboxConfig;
11
12#[derive(Debug, Clone)]
14pub struct SandboxPolicy {
15 pub project_dir: PathBuf,
17 pub allowed_write_paths: Vec<PathBuf>,
19 pub forbidden_paths: Vec<PathBuf>,
21 pub allow_network: bool,
23}
24
25impl SandboxPolicy {
26 pub fn from_config(config: &SandboxConfig, project_dir: &Path) -> Self {
31 let home = std::env::var_os("HOME").map(PathBuf::from);
32 let expand = |s: &String| expand_path(s.as_str(), &home, project_dir);
33
34 Self {
35 project_dir: project_dir.to_path_buf(),
36 allowed_write_paths: config.allowed_write_paths.iter().map(expand).collect(),
37 forbidden_paths: config.forbidden_paths.iter().map(expand).collect(),
38 allow_network: config.allow_network,
39 }
40 }
41}
42
43fn expand_path(raw: &str, home: &Option<PathBuf>, project_dir: &Path) -> PathBuf {
44 if let Some(rest) = raw.strip_prefix("~/")
45 && let Some(home) = home
46 {
47 return home.join(rest);
48 }
49 if raw == "~"
50 && let Some(home) = home
51 {
52 return home.clone();
53 }
54
55 let p = PathBuf::from(raw);
56 if p.is_absolute() {
57 p
58 } else {
59 project_dir.join(p)
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn expands_tilde_to_home() {
69 let home = Some(PathBuf::from("/Users/alice"));
70 let project = PathBuf::from("/work/repo");
71 assert_eq!(
72 expand_path("~/.cache/agent-code", &home, &project),
73 PathBuf::from("/Users/alice/.cache/agent-code")
74 );
75 assert_eq!(
76 expand_path("~", &home, &project),
77 PathBuf::from("/Users/alice")
78 );
79 }
80
81 #[test]
82 fn absolute_paths_unchanged() {
83 let home = Some(PathBuf::from("/Users/alice"));
84 let project = PathBuf::from("/work/repo");
85 assert_eq!(expand_path("/tmp", &home, &project), PathBuf::from("/tmp"));
86 }
87
88 #[test]
89 fn relative_paths_join_project_dir() {
90 let home = Some(PathBuf::from("/Users/alice"));
91 let project = PathBuf::from("/work/repo");
92 assert_eq!(
93 expand_path("target", &home, &project),
94 PathBuf::from("/work/repo/target")
95 );
96 }
97
98 #[test]
99 fn missing_home_leaves_tilde() {
100 let home: Option<PathBuf> = None;
102 let project = PathBuf::from("/work/repo");
103 assert_eq!(
105 expand_path("~/foo", &home, &project),
106 PathBuf::from("/work/repo/~/foo")
107 );
108 }
109
110 #[test]
111 fn from_config_resolves_paths() {
112 unsafe { std::env::set_var("HOME", "/Users/test") };
115 let cfg = SandboxConfig {
116 enabled: true,
117 strategy: "seatbelt".to_string(),
118 allowed_write_paths: vec!["/tmp".to_string(), "~/.cache/agent-code".to_string()],
119 forbidden_paths: vec!["~/.ssh".to_string()],
120 allow_network: false,
121 };
122 let policy = SandboxPolicy::from_config(&cfg, Path::new("/work/repo"));
123 assert_eq!(policy.project_dir, PathBuf::from("/work/repo"));
124 assert_eq!(
125 policy.allowed_write_paths,
126 vec![
127 PathBuf::from("/tmp"),
128 PathBuf::from("/Users/test/.cache/agent-code"),
129 ]
130 );
131 assert_eq!(
132 policy.forbidden_paths,
133 vec![PathBuf::from("/Users/test/.ssh")]
134 );
135 assert!(!policy.allow_network);
136 }
137}