返回 CodeWhale
tests.rs
根目录 / crates / tui / src / sandbox / seatbelt / tests.rs
1 use super::*;
2
3 /// #4085: an inherited read-write extension is necessary but never sufficient
4 /// for WorkspaceWrite. Every extension-backed write must also match one
5 /// approved root and retain that root's protected-subpath exclusions.
6 #[test]
7 fn file_provider_extensions_intersect_each_workspace_root_and_exclusions() {
8 assert!(
9 is_available(),
10 "UNRUN: macOS sandbox-exec is unavailable; generated policy was not parsed"
11 );
12
13 let fixture = tempfile::tempdir().expect("create policy fixture");
14 let workspace = fixture.path().join("workspace");
15 let additional_root = fixture.path().join("additional-root");
16 std::fs::create_dir_all(workspace.join(".codewhale")).expect("create protected workspace path");
17 std::fs::create_dir_all(additional_root.join(".deepseek"))
18 .expect("create protected additional-root path");
19
20 let policy = SandboxPolicy::WorkspaceWrite {
21 writable_roots: vec![additional_root],
22 network_access: false,
23 exclude_tmpdir: true,
24 exclude_slash_tmp: true,
25 };
26 let workspace_write = generate_policy(&policy, &workspace);
27 let read_only = generate_policy(&SandboxPolicy::ReadOnly, &workspace);
28
29 for policy in [&workspace_write, &read_only] {
30 assert!(policy.contains("(version 1)"));
31 assert!(policy.contains("(deny default)"));
32 assert!(policy.contains("(allow file-read*)"));
33 assert!(policy.contains(r#"(allow file-read* (extension "com.apple.app-sandbox.read"))"#));
34 assert!(
35 policy.contains(r#"(allow file-read* (extension "com.apple.app-sandbox.read-write"))"#)
36 );
37 }
38 assert!(!workspace_write.contains("network-outbound"));
39
40 for root_index in 0..=1 {
41 let expected = format!(
42 r#"(require-all (extension "com.apple.app-sandbox.read-write") (subpath (param "WRITABLE_ROOT_{root_index}")) (require-not (subpath (param "WRITABLE_ROOT_{root_index}_RO_0"))))"#
43 );
44 assert!(
45 workspace_write.contains(&expected),
46 "extension write must retain root {root_index} and its exclusion:\n{workspace_write}"
47 );
48 }
49 let read_write_extension = r#"(extension "com.apple.app-sandbox.read-write")"#;
50 assert_eq!(
51 workspace_write.matches(read_write_extension).count(),
52 3,
53 "one read rule plus exactly one root-scoped write rule per approved root"
54 );
55 assert!(workspace_write.contains("file-write*"));
56 assert!(!workspace_write.lines().any(|line| {
57 line.trim() == r#"(allow file-write* (extension "com.apple.app-sandbox.read-write"))"#
58 }));
59 assert!(!read_only.contains(r#"file-write* (extension "com.apple.app-sandbox.read-write")"#));
60 assert_eq!(read_only.matches(read_write_extension).count(), 1);
61 assert!(!read_only.contains("WRITABLE_ROOT"));
62
63 let args = create_seatbelt_args(vec!["/usr/bin/true".to_string()], &policy, &workspace);
64 let output = Command::new(SANDBOX_EXEC_PATH)
65 .args(args)
66 .current_dir(&workspace)
67 .output()
68 .expect("parse generated policy with sandbox-exec");
69 assert!(
70 output.status.success(),
71 "sandbox-exec rejected generated intersection policy: {}",
72 String::from_utf8_lossy(&output.stderr)
73 );
74 }
75
76 /// Hermetic command/policy-shape coverage for the six operations reported in
77 /// #4085. Each operation gets a fresh fixture so an early failure cannot hide
78 /// later results. This is not physical File Provider acceptance.
79 #[test]
80 fn file_provider_synthetic_operations_are_independent_under_seatbelt() {
81 assert!(
82 is_available(),
83 "UNRUN: macOS sandbox-exec is unavailable; no synthetic operation evidence collected"
84 );
85
86 #[derive(Clone, Copy, Debug)]
87 enum Operation {
88 Mkdir,
89 Write,
90 Read,
91 Grep,
92 DeleteFile,
93 DeleteDirectory,
94 }
95
96 let mut failures = Vec::new();
97 for operation in [
98 Operation::Mkdir,
99 Operation::Write,
100 Operation::Read,
101 Operation::Grep,
102 Operation::DeleteFile,
103 Operation::DeleteDirectory,
104 ] {
105 let fixture = tempfile::tempdir().expect("create independent operation fixture");
106 let workspace = fixture
107 .path()
108 .join("Library/CloudStorage/TestProvider/Workspace");
109 std::fs::create_dir_all(&workspace).expect("create synthetic CloudStorage workspace");
110 let target = workspace.join("target");
111 let source = workspace.join("source");
112
113 let command = match operation {
114 Operation::Mkdir => vec![
115 "/bin/mkdir".to_string(),
116 target.to_string_lossy().into_owned(),
117 ],
118 Operation::Write => {
119 std::fs::write(&source, b"file-provider\n").expect("seed copy source");
120 vec![
121 "/bin/cp".to_string(),
122 source.to_string_lossy().into_owned(),
123 target.to_string_lossy().into_owned(),
124 ]
125 }
126 Operation::Read => {
127 std::fs::write(&target, b"file-provider\n").expect("seed read target");
128 vec![
129 "/bin/cat".to_string(),
130 target.to_string_lossy().into_owned(),
131 ]
132 }
133 Operation::Grep => {
134 std::fs::write(&target, b"file-provider\n").expect("seed grep target");
135 vec![
136 "/usr/bin/grep".to_string(),
137 "-q".to_string(),
138 "file-provider".to_string(),
139 target.to_string_lossy().into_owned(),
140 ]
141 }
142 Operation::DeleteFile => {
143 std::fs::write(&target, b"file-provider\n").expect("seed deletion target");
144 vec!["/bin/rm".to_string(), target.to_string_lossy().into_owned()]
145 }
146 Operation::DeleteDirectory => {
147 std::fs::create_dir(&target).expect("seed directory deletion target");
148 vec![
149 "/bin/rmdir".to_string(),
150 target.to_string_lossy().into_owned(),
151 ]
152 }
153 };
154
155 let args = create_seatbelt_args(command, &SandboxPolicy::default(), &workspace);
156 let output = Command::new(SANDBOX_EXEC_PATH)
157 .args(args)
158 .current_dir(&workspace)
159 .output()
160 .expect("execute sandboxed operation");
161
162 let effect_matches = match operation {
163 Operation::Mkdir => target.is_dir(),
164 Operation::Write => {
165 matches!(std::fs::read(&target), Ok(bytes) if bytes == b"file-provider\n")
166 }
167 Operation::Read => output.stdout == b"file-provider\n",
168 Operation::Grep => true,
169 Operation::DeleteFile | Operation::DeleteDirectory => !target.exists(),
170 };
171 if !output.status.success() || !effect_matches {
172 failures.push(format!(
173 "{operation:?}: status={:?}, stderr={}",
174 output.status.code(),
175 String::from_utf8_lossy(&output.stderr)
176 ));
177 }
178 }
179
180 assert!(
181 failures.is_empty(),
182 "independent sandbox operations failed:\n{}",
183 failures.join("\n")
184 );
185 }
186
186 lines RUST