1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! Windows enforcement tests. On other platforms these are inert placeholders
//! so the shared test binary stays green in CI; the real assertions run on
//! Windows runners where the experimental AppContainer backend is available.
//! Every Windows test starts with a capability gate over `vetto doctor` and
//! skips with an explicit reason instead of asserting against a run that the
//! fail-closed backend itself would refuse.
#[cfg(not(target_os = "windows"))]
#[test]
fn windows_enforcement_suite_not_applicable_on_this_platform() {
// Honest no-op: the Windows process sandbox backend only exists on
// Windows (see release CI matrix).
}
#[cfg(target_os = "windows")]
mod windows_only {
use std::net::TcpListener;
use crate::common::{doctor_output, run_vetto_in, stderr, stdout, write_file, TempProject};
/// True only when doctor reports the full Windows process-sandbox stack:
/// the AppContainer APIs and the experimental
/// `Experimental_CreateProcessInSandbox` export. Without them the backend
/// refuses to run, so enforcement tests must skip honestly.
fn backend_available() -> bool {
let doctor = doctor_output();
doctor.contains("appcontainer-api=yes")
&& doctor.contains("experimental-process-sandbox=yes")
}
const BACKEND_SKIP: &str = "SKIP: Windows AppContainer/experimental sandbox backend is unavailable (doctor did not report appcontainer-api=yes and experimental-process-sandbox=yes)";
#[test]
fn read_outside_granted_roots_fails() {
if !backend_available() {
eprintln!("{BACKEND_SKIP}");
return;
}
// The sentinel file lives in a directory next to (not inside) the
// sandboxed project. The default Windows grants are the project and
// the drive-root tmp path, so this file is outside every granted
// root.
let external = TempProject::new("win-read-external");
let outside = external.path().join("outside.txt");
write_file(&outside, "VETTO-IT-OUTSIDE-CONTENT");
let outside_text = outside.to_string_lossy().into_owned();
let project = TempProject::new("win-read-project");
let output = run_vetto_in(
project.path(),
&[
"--tui=none",
"--",
"cmd",
"/c",
"type",
outside_text.as_str(),
],
);
// The honest property is content absence: the child may fail, be
// denied by AppContainer, or print an error, but the sentinel text
// must never reach stdout through the inherited stdio.
let out = stdout(&output);
assert!(
!out.contains("VETTO-IT-OUTSIDE-CONTENT"),
"content from outside the granted roots reached stdout: {out}"
);
}
#[test]
fn write_outside_granted_roots_fails() {
if !backend_available() {
eprintln!("{BACKEND_SKIP}");
return;
}
let external = TempProject::new("win-write-external");
let outside = external.path().join("outside.txt");
// Pre-existing sentinel: the run must leave it byte-for-byte
// unchanged, which also covers the not-created case via absence.
write_file(&outside, "VETTO-IT-ORIGINAL-CONTENT");
let outside_text = outside.to_string_lossy().into_owned();
let project = TempProject::new("win-write-project");
let output = run_vetto_in(
project.path(),
&[
"--tui=none",
"--",
"cmd",
"/c",
"echo",
"x>",
outside_text.as_str(),
],
);
let after = std::fs::read_to_string(&outside).unwrap_or_default();
assert_eq!(
after,
"VETTO-IT-ORIGINAL-CONTENT",
"file outside the granted roots was created or modified; stderr: {}",
stderr(&output)
);
}
#[test]
fn network_off_blocks_loopback() {
if !backend_available() {
eprintln!("{BACKEND_SKIP}");
return;
}
// This asserts the default-deny NetworkPolicy compiled into the spec:
// an AppContainer without a network capability cannot reach even
// loopback, so the connection attempt must raise instead of
// succeeding.
let listener = TcpListener::bind("127.0.0.1:0").expect("bind loopback listener");
let port = listener.local_addr().expect("listener local addr").port();
// Keep the listener bound for the whole run so a successful connect
// (into the backlog) is the only way to print CONNECTED.
let _keep_bound = &listener;
let probe = std::process::Command::new("powershell")
.args(["-NoProfile", "-Command", "exit 0"])
.output();
let powershell_ready = probe.map(|o| o.status.success()).unwrap_or(false);
if !powershell_ready {
eprintln!(
"SKIP: powershell is unavailable on this runner; cannot exercise the default-deny network policy from inside the sandbox"
);
return;
}
let script = format!(
"try {{ (New-Object Net.Sockets.TcpClient('127.0.0.1',{port})).Close(); 'CONNECTED' }} catch {{ 'BLOCKED' }}"
);
let project = TempProject::new("win-net-project");
let output = run_vetto_in(
project.path(),
&[
"--tui=none",
"--",
"powershell",
"-NoProfile",
"-Command",
script.as_str(),
],
);
let out = stdout(&output);
assert!(
!out.contains("CONNECTED"),
"loopback connect succeeded inside the sandbox; stdout: {out} stderr: {}",
stderr(&output)
);
}
#[test]
fn job_limits_do_not_break_small_sessions() {
if !backend_available() {
eprintln!("{BACKEND_SKIP}");
return;
}
// as=2 GiB becomes the Job memory limit and procs=256 the active
// process limit; a successful trivial session proves those flag bits
// construct a Job Object the experimental launcher accepts.
let project = TempProject::new("win-limits-project");
let output = run_vetto_in(
project.path(),
&[
"--limits",
"as=2147483648,procs=256",
"--tui=none",
"--",
"cmd",
"/c",
"echo",
"ok",
],
);
assert!(
output.status.success(),
"a small session under explicit job limits must succeed; stderr: {}",
stderr(&output)
);
assert!(
stdout(&output).contains("ok"),
"echo output missing from stdout: {}",
stdout(&output)
);
}
/// Positive control for read_outside_granted_roots_fails: the SAME read
/// against a file INSIDE the granted project root must succeed, proving
/// the sandbox denies by scope and not by blanket file blocking.
#[test]
fn read_inside_granted_root_succeeds() {
if !backend_available() {
eprintln!("{BACKEND_SKIP}");
return;
}
let proj = TempProject::new("win-positive-control");
write_file(&proj.path().join("inside.txt"), "VETTO-INSIDE-GRANT");
let output = run_vetto_in(
proj.path(),
&["--tui=none", "--", "cmd", "/c", "type", "inside.txt"],
);
assert!(
stdout(&output).contains("VETTO-INSIDE-GRANT"),
"read inside the granted root must work; stderr: {}",
stderr(&output)
);
}
}