Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,39 @@ jobs:
uses: taiki-e/install-action@v2
with:
tool: cargo-deny@0.20.2
- name: Enable legacy TIOCSTI for the isolation probes
run: |
# The runner's kernel may refuse the write (dev.tty.legacy_tiocsti has no
# namespace, so a container or a locked-down kernel cannot set it). Log the
# outcome and never fail the job: the probe reports UNVERIFIED in that case.
sudo sysctl -w dev.tty.legacy_tiocsti=1 || echo "could not write dev.tty.legacy_tiocsti (exit $?): the TIOCSTI probe reports UNVERIFIED"
sysctl -n dev.tty.legacy_tiocsti || echo "could not read dev.tty.legacy_tiocsti (exit $?)"
- name: Repository acceptance suite
env:
COMMANDAGENT_PROBE_VERDICT_FILE: ${{ runner.temp }}/probe-verdicts.txt
run: bash scripts/ci.sh
- name: Report isolation probe verdicts
if: always()
env:
COMMANDAGENT_PROBE_VERDICT_FILE: ${{ runner.temp }}/probe-verdicts.txt
run: |
# A passing test's stdout is captured by cargo test, and $GITHUB_STEP_SUMMARY
# is not readable through gh or the REST API, so print the verdict file and
# annotate every UNVERIFIED line. This step never fails the job.
verdicts="${COMMANDAGENT_PROBE_VERDICT_FILE}"
if [ ! -s "$verdicts" ]; then
echo "::warning::no isolation probe verdict was recorded in $verdicts: the probes did not run, or the file is empty"
exit 0
fi
echo "isolation probe verdicts ($verdicts):"
cat "$verdicts"
while IFS= read -r line; do
case "$line" in
*[Uu][Nn][Vv][Ee][Rr][Ii][Ff][Ii][Ee][Dd]*)
echo "::warning::$line"
;;
esac
done < "$verdicts"

gui-dashboard:
name: GUI Dashboard
Expand Down
56 changes: 40 additions & 16 deletions tests/issue502_os_sandbox_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,25 @@ fn run_tiocsti_attempt(slave: &std::fs::File, inject: char, wrapped: bool) -> St
}
}

/// Append one probe verdict line to the file named by
/// `COMMANDAGENT_PROBE_VERDICT_FILE` when that variable is set, so a CI step can
/// print the verdicts and turn each `UNVERIFIED` line into an annotation.
/// `cargo test` captures a passing test's stdout, and `$GITHUB_STEP_SUMMARY` is
/// not readable through `gh` or the REST API, so neither shows the verdict on
/// its own. A write failure never fails a test, exactly like the
/// `$GITHUB_STEP_SUMMARY` recording, and an unset variable changes nothing.
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn append_probe_verdict(line: &str) {
let Some(path) = std::env::var_os("COMMANDAGENT_PROBE_VERDICT_FILE") else {
return;
};
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.and_then(|mut file| std::io::Write::write_all(&mut file, line.as_bytes()));
}

/// Report an unmeasurable `TIOCSTI` probe on stdout, like the other probes, so
/// the outer test can find the verdict instead of reading it as missing; the
/// `$GITHUB_STEP_SUMMARY` line still records it, so it is not a silent pass.
Expand All @@ -1451,16 +1470,18 @@ fn record_unverified(reason: &str) {

/// Append the `TIOCSTI` verdict to `$GITHUB_STEP_SUMMARY` when it is set, with
/// the kernel's `legacy_tiocsti` value when the file exists, so a runner can tell
/// a measured denial from an unverifiable environment.
/// a measured denial from an unverifiable environment. The same line also goes to
/// `$COMMANDAGENT_PROBE_VERDICT_FILE` so the verdict reaches the job log.
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn record_tiocsti_step_summary(status: &str) {
let legacy = std::fs::read_to_string("/proc/sys/dev/tty/legacy_tiocsti")
.map(|value| value.trim().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let line = format!("issue667/tiocsti: {status}; legacy_tiocsti={legacy}\n");
append_probe_verdict(&line);
let Some(path) = std::env::var_os("GITHUB_STEP_SUMMARY") else {
return;
};
let line = format!("issue667/tiocsti: {status}; legacy_tiocsti={legacy}\n");
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
Expand Down Expand Up @@ -1555,13 +1576,14 @@ fn issue667_stdin_probe() {
let sandboxed = run_stdin_child(true);
os_sandbox::reset_for_test();
assert_stdin_is_detached(&sandboxed);
println!(
"issue667-stdin: BLOCKED (both the isolated and unisolated launches detach a terminal stdin)"
);
let verdict = "both the isolated and unisolated launches detach a terminal stdin";
append_probe_verdict(&format!("issue667/stdin: blocked ({verdict})\n"));
println!("issue667-stdin: BLOCKED ({verdict})");
} else {
println!(
"issue667-stdin: BLOCKED (the unisolated launch detaches a terminal stdin; the sandbox backend is unavailable)"
);
let verdict =
"the unisolated launch detaches a terminal stdin; the sandbox backend is unavailable";
append_probe_verdict(&format!("issue667/stdin: blocked ({verdict})\n"));
println!("issue667-stdin: BLOCKED ({verdict})");
}
}

Expand Down Expand Up @@ -1713,13 +1735,13 @@ fn issue667_devnull_fd_probe() {
let sandboxed = run_devnull_fd_child(true, false);
os_sandbox::reset_for_test();
assert_no_devnull_descriptor(&sandboxed);
println!(
"issue667-devnull-fd: BLOCKED (both the isolated and unisolated launches close the /dev/null descriptor)"
);
let verdict = "both the isolated and unisolated launches close the /dev/null descriptor";
append_probe_verdict(&format!("issue667/devnull-fd: blocked ({verdict})\n"));
println!("issue667-devnull-fd: BLOCKED ({verdict})");
} else {
println!(
"issue667-devnull-fd: BLOCKED (the unisolated launch closes the /dev/null descriptor; the sandbox backend is unavailable)"
);
let verdict = "the unisolated launch closes the /dev/null descriptor; the sandbox backend is unavailable";
append_probe_verdict(&format!("issue667/devnull-fd: blocked ({verdict})\n"));
println!("issue667-devnull-fd: BLOCKED ({verdict})");
}
}

Expand Down Expand Up @@ -1970,13 +1992,15 @@ fn terminal_read_token(mode: &str, sandbox: bool) -> String {

/// Append the terminal-read verdict to `$GITHUB_STEP_SUMMARY` when it is set, so
/// a run that could not measure the denial records why instead of passing
/// silently.
/// silently. The same line also goes to `$COMMANDAGENT_PROBE_VERDICT_FILE` so the
/// verdict reaches the job log.
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn record_terminal_read_step_summary(status: &str) {
let line = format!("issue667/terminal-read: {status}\n");
append_probe_verdict(&line);
let Some(path) = std::env::var_os("GITHUB_STEP_SUMMARY") else {
return;
};
let line = format!("issue667/terminal-read: {status}\n");
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
Expand Down
Loading