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
//! Spawn a user-supplied command (e.g. `publisher.cmd`) with a clean,
//! whitelisted environment.
//!
//! Centralised here so the `Command::new(<arbitrary>)` shell-out lives
//! inside the module-boundaries allow-list. Inlining this in the CLI
//! crate would put `Command::new` outside the allow-list and counts
//! as a boundary violation.
use std::ffi::OsStr;
use std::process::Command;
use anyhow::Result;
/// Environment variables that are inherited from the parent process
/// when constructing a sandboxed `Command`. Anything else must be
/// explicitly added via `Command::env`.
///
/// This whitelist exists to prevent accidental leakage of release
/// credentials (`GITHUB_TOKEN`, `COSIGN_*`, signing keys, etc.) into
/// arbitrary user-supplied commands.
pub const ENV_WHITELIST: &[&str] = &[
"HOME",
"USER",
"USERPROFILE",
"TMPDIR",
"TMP",
"TEMP",
"PATH",
"SYSTEMROOT",
];
/// Construct a `Command` whose argv is `argv` and whose environment is
/// reset to the [`ENV_WHITELIST`] subset of the parent's env. The first
/// element of `argv` is the program; the rest are arguments. The caller
/// is responsible for adding any further env vars / cwd / I/O config
/// before invoking `output()`.
///
/// Returns `Err` when `argv` is empty — surfacing a clear error at the
/// allow-listed boundary is preferable to deferring failure to the
/// kernel via an empty `program` path.
pub fn whitelisted<S: AsRef<OsStr>>(argv: &[S]) -> Result<Command> {
anyhow::ensure!(!argv.is_empty(), "user command argv cannot be empty");
let program = argv[0].as_ref();
let mut cmd = Command::new(program);
if argv.len() > 1 {
cmd.args(&argv[1..]);
}
cmd.env_clear();
for key in ENV_WHITELIST {
if let Ok(val) = std::env::var(key) {
cmd.env(key, val);
}
}
Ok(cmd)
}