git-ents.gitmain
⌘K
foforge
unsandboxed.rs129 lines · 4.7 KB · rusthistorycomment on this file
1//! Host-direct execution, with no sandbox at all (`effect.execution`:
2//! "Host-direct execution... MUST require an explicit `--unsandboxed` flag
3//! and MUST be available only locally, never on canonical hosted
4//! infrastructure").
5//!
6//! This module only provides the [`Executor`] implementation; enforcing
7//! that it is reachable solely behind an explicit flag, and never wired at
8//! a hosted composition root, is `roots.local`'s and the future CLI's job
9//! (`roots.config-isolation`: selection happens at the root, never inside
10//! a library).
11
12use std::process::Command;
13
14use crate::error::{Error, Result};
15use crate::executor::{Executor, RunOutput, RunStatus, SandboxInputs, activate};
16
17/// [`Executor`] running a command directly on the host, with the tested
18/// tree's checkout as its working directory and every declared toolchain's
19/// `bin/` activated on `PATH` — no isolation whatsoever.
20#[derive(Debug, Clone, Copy, Default)]
21pub struct UnsandboxedExecutor;
22
23impl Executor for UnsandboxedExecutor {
24 fn run(&self, inputs: &SandboxInputs<'_>) -> Result<RunOutput> {
25 let dirs: Vec<(String, String)> = inputs
26 .toolchains
27 .iter()
28 .map(|(name, dir)| (name.clone(), dir.display().to_string()))
29 .collect();
30 let output = Command::new("sh")
31 .arg("-c")
32 .arg(activate(inputs.command, &dirs))
33 .current_dir(inputs.workdir)
34 // Set directly on the child's environment rather than folded
35 // into the shell script `activate` built above: a secret (a
36 // BYOK credential, `SandboxInputs::env`) never needs shell
37 // quoting at all this way, unlike the sandboxed backends that
38 // must ship the whole command as one script string
39 // (`crate::executor::inject_env`).
40 .envs(inputs.env.iter().cloned())
41 .output()
42 .map_err(|e| Error::Spawn {
43 program: "sh".to_owned(),
44 detail: e.to_string(),
45 })?;
46 let mut log = String::from_utf8_lossy(&output.stdout).into_owned();
47 log.push_str(&String::from_utf8_lossy(&output.stderr));
48 let status = if output.status.success() {
49 RunStatus::Pass
50 } else {
51 RunStatus::Fail
52 };
53 Ok(RunOutput { status, log })
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 #![allow(clippy::expect_used, reason = "unit test")]
60
61 use std::path::PathBuf;
62
63 use rstest::rstest;
64
65 use super::*;
66
67 #[rstest]
68 // @relation(effect.execution, scope=function, role=Verifies)
69 fn unsandboxed_reports_pass_on_exit_zero() {
70 let dir = tempfile::tempdir().expect("tempdir");
71 let inputs = SandboxInputs {
72 workdir: dir.path(),
73 toolchains: &[],
74 command: "true",
75 env: &[],
76 };
77 let output = UnsandboxedExecutor.run(&inputs).expect("runs");
78 assert_eq!(output.status, RunStatus::Pass);
79 }
80
81 #[rstest]
82 // @relation(effect.execution, scope=function, role=Verifies)
83 fn unsandboxed_reports_fail_on_nonzero_exit_never_as_an_error() {
84 let dir = tempfile::tempdir().expect("tempdir");
85 let inputs = SandboxInputs {
86 workdir: dir.path(),
87 toolchains: &[],
88 command: "false",
89 env: &[],
90 };
91 let output = UnsandboxedExecutor
92 .run(&inputs)
93 .expect("a completed run is never Err");
94 assert_eq!(output.status, RunStatus::Fail);
95 }
96
97 #[rstest]
98 // @relation(effect.execution, effect.toolchains, scope=function, role=Verifies)
99 fn unsandboxed_activates_declared_toolchains_on_path() {
100 let dir = tempfile::tempdir().expect("tempdir");
101 let bin = dir.path().join("bin");
102 std::fs::create_dir_all(&bin).expect("mkdir");
103 let toolchains = vec![("t".to_owned(), bin)];
104 let inputs = SandboxInputs {
105 workdir: dir.path(),
106 toolchains: &toolchains,
107 command: "echo $PATH",
108 env: &[],
109 };
110 let output = UnsandboxedExecutor.run(&inputs).expect("runs");
111 assert!(output.log.contains("bin"));
112 let _: Vec<(String, PathBuf)> = toolchains;
113 }
114
115 #[rstest]
116 // @relation(roots.config-isolation, scope=function, role=Verifies)
117 fn unsandboxed_injects_env_directly_on_the_child_process() {
118 let dir = tempfile::tempdir().expect("tempdir");
119 let env = vec![("ENTS_TEST_CREDENTIAL".to_owned(), "sk-ant-test".to_owned())];
120 let inputs = SandboxInputs {
121 workdir: dir.path(),
122 toolchains: &[],
123 command: "printf '%s' \"$ENTS_TEST_CREDENTIAL\"",
124 env: &env,
125 };
126 let output = UnsandboxedExecutor.run(&inputs).expect("runs");
127 assert_eq!(output.log, "sk-ant-test");
128 }
129}