use std::cell::Cell;
use super::*;
fn isolated(home: &Path, account: &Path) -> SandboxEnv {
SandboxEnv {
disallowed_names: Vec::new(),
data_dir_override: Some(home.join("data").into_os_string()),
sandbox_flag: Some(OsString::from("1")),
home: Some(home.to_path_buf()),
account_home: Some(account.to_path_buf()),
}
}
fn two_dirs() -> (tempfile::TempDir, tempfile::TempDir) {
(
tempfile::tempdir().expect("home tempdir"),
tempfile::tempdir().expect("account tempdir"),
)
}
#[test]
fn disallowed_names_admits_only_the_closed_allowlist() {
let allowed = [
"HOME",
"PATH",
"TRUSTY_DATA_DIR_OVERRIDE",
"TRUSTY_MPM_ADDR",
"TMPDIR",
"TERM",
"LANG",
"LC_ALL",
"LC_CTYPE",
"LC_TELEPHONE",
"USER",
"LOGNAME",
"SHELL",
"RUST_LOG",
"TRUSTY_SANDBOX",
];
let refused = [
"TELEGRAM_BOT_TOKEN",
"OPENAI_API_KEY",
"GITHUB_PAT",
"DATABASE_URL",
"LC_API_KEY",
"home",
"TRUSTY_BUGREPORT_TOKEN_FILE",
"SSH_AUTH_SOCK",
"TELEGRAM_BOT_TOKEN",
];
let names = allowed.iter().chain(refused.iter()).map(OsString::from);
assert_eq!(
disallowed_names(names),
vec![
"DATABASE_URL",
"GITHUB_PAT",
"LC_API_KEY",
"OPENAI_API_KEY",
"SSH_AUTH_SOCK",
"TELEGRAM_BOT_TOKEN",
"TRUSTY_BUGREPORT_TOKEN_FILE",
"home",
]
);
}
#[test]
fn a_non_utf8_name_is_refused() {
use std::os::unix::ffi::OsStringExt;
let name = OsString::from_vec(vec![b'K', 0xff]);
assert_eq!(disallowed_names(std::iter::once(name)).len(), 1);
}
#[test]
fn only_a_well_formed_cf_text_encoding_is_allowed() {
let cf = OsString::from("__CF_USER_TEXT_ENCODING");
for good in ["0x1F5:0x0:0x0", "0x0:0x0:0x0", "1F5:0:0"] {
assert!(is_cf_text_encoding(&cf, &OsString::from(good)), "{good}");
}
for bad in [
"sk-fake-9121",
"0x1F5:0x0",
"0x1F5:0x0:0x0:0x0",
"0xZZ:0x0:0x0",
"",
] {
assert!(!is_cf_text_encoding(&cf, &OsString::from(bad)), "{bad}");
}
let other = OsString::from("CF_USER_TEXT_ENCODING");
assert!(!is_cf_text_encoding(
&other,
&OsString::from("0x1F5:0x0:0x0")
));
}
#[test]
fn a_variable_outside_the_allowlist_refuses_and_names_only_the_variables() {
let (home, account) = two_dirs();
let mut env = isolated(home.path(), account.path());
env.disallowed_names = vec!["OPENAI_API_KEY".into(), "TELEGRAM_BOT_TOKEN".into()];
let err = enter_with(&env, || panic!("a refused sandbox must not latch"))
.expect_err("a secret-carrying environment must refuse");
let msg = err.to_string();
assert!(msg.contains("TELEGRAM_BOT_TOKEN"), "{msg}");
assert!(msg.contains("OPENAI_API_KEY"), "{msg}");
assert!(msg.contains("values not shown"), "{msg}");
assert!(msg.contains("#9121"), "{msg}");
assert!(msg.contains("outside the sandbox allowlist"), "{msg}");
assert!(
msg.contains("sandbox launcher (`env -i` + allowlist)"),
"{msg}"
);
assert!(!msg.contains("scripts/"), "{msg}");
}
#[test]
fn refuses_without_a_data_dir_override() {
let (home, account) = two_dirs();
for value in [None, Some(OsString::new())] {
let mut env = isolated(home.path(), account.path());
env.data_dir_override = value.clone();
assert_eq!(
refusals(&env),
vec![Refusal::NoDataDirOverride],
"override {value:?}"
);
}
}
#[test]
fn refuses_unless_the_sandbox_flag_is_exactly_one() {
use std::os::unix::ffi::OsStringExt;
let (home, account) = two_dirs();
let wrong = [
None,
Some(OsString::new()),
Some(OsString::from("0")),
Some(OsString::from("true")),
Some(OsString::from("01")),
Some(OsString::from(" 1")),
Some(OsString::from("1\n")),
Some(OsString::from_vec(vec![b'1', 0xff])),
];
for value in wrong {
let mut env = isolated(home.path(), account.path());
env.sandbox_flag = value.clone();
assert_eq!(
refusals(&env),
vec![Refusal::SandboxFlagNotSet],
"flag {value:?}"
);
}
}
#[test]
fn a_wrong_sandbox_flag_refuses_without_printing_the_value() {
let (home, account) = two_dirs();
let mut env = isolated(home.path(), account.path());
env.sandbox_flag = Some(OsString::from("fake-9178-flag-value"));
let msg = enter_with(&env, || panic!("a refused sandbox must not latch"))
.expect_err("a wrong flag must refuse")
.to_string();
assert!(msg.contains("TRUSTY_SANDBOX"), "{msg}");
assert!(!msg.contains("fake-9178-flag-value"), "{msg}");
}
#[test]
fn refuses_when_home_is_unset() {
let (home, account) = two_dirs();
for value in [None, Some(PathBuf::new())] {
let mut env = isolated(home.path(), account.path());
env.home = value.clone();
assert_eq!(refusals(&env), vec![Refusal::NoHome], "home {value:?}");
}
}
#[test]
fn refuses_when_home_is_the_account_home() {
let (home, _) = two_dirs();
let env = isolated(home.path(), home.path());
assert_eq!(
refusals(&env),
vec![Refusal::HomeIsAccountHome(home.path().to_path_buf())]
);
}
#[test]
fn refuses_when_home_symlinks_to_the_account_home() {
let (account, scratch) = two_dirs();
let link = scratch.path().join("home-link");
std::os::unix::fs::symlink(account.path(), &link).expect("symlink");
let env = isolated(&link, account.path());
assert_eq!(refusals(&env), vec![Refusal::HomeIsAccountHome(link)]);
}
#[test]
fn refuses_when_the_account_home_is_unknown() {
let (home, account) = two_dirs();
for value in [None, Some(PathBuf::new())] {
let mut env = isolated(home.path(), account.path());
env.account_home = value.clone();
assert_eq!(
refusals(&env),
vec![Refusal::AccountHomeUnknown],
"account {value:?}"
);
}
}
#[test]
fn refuses_when_home_does_not_exist() {
let (home, account) = two_dirs();
let missing = home.path().join("does-not-exist");
let mut env = isolated(home.path(), account.path());
env.home = Some(missing.clone());
assert_eq!(refusals(&env), vec![Refusal::HomeUnresolvable(missing)]);
}
#[test]
fn every_refusal_is_reported_together() {
let env = SandboxEnv {
disallowed_names: vec!["TELEGRAM_BOT_TOKEN".into()],
..SandboxEnv::default()
};
let msg = enter_with(&env, || panic!("must not latch"))
.expect_err("an empty environment must refuse")
.to_string();
assert!(msg.contains("TELEGRAM_BOT_TOKEN"), "{msg}");
assert!(msg.contains(DATA_DIR_OVERRIDE_ENV), "{msg}");
assert!(msg.contains(SANDBOX_ENV_VAR), "{msg}");
assert!(msg.contains("$HOME is not set"), "{msg}");
}
#[test]
fn an_isolated_clean_environment_enters_and_latches() {
let (home, account) = two_dirs();
let latched = Cell::new(0);
enter_with(&isolated(home.path(), account.path()), || {
latched.set(latched.get() + 1);
})
.expect("an isolated environment enters sandbox mode");
assert_eq!(latched.get(), 1);
}
#[test]
fn a_refused_sandbox_never_latches() {
let (home, account) = two_dirs();
let mut env = isolated(home.path(), account.path());
env.data_dir_override = None;
let latched = Cell::new(false);
let _ = enter_with(&env, || latched.set(true));
assert!(!latched.get(), "a refused sandbox set the credential latch");
}
#[test]
fn sandbox_never_spawns_the_telegram_bot() {
let spawned = Cell::new(false);
let handle = gate_channel_pollers(true, || {
spawned.set(true);
Some(())
});
assert!(handle.is_none());
assert!(!spawned.get(), "the bot spawn ran in sandbox mode");
}
#[test]
fn outside_sandbox_the_bot_spawn_runs() {
let spawned = Cell::new(false);
let handle = gate_channel_pollers(false, || {
spawned.set(true);
Some(())
});
assert!(handle.is_some());
assert!(spawned.get());
}