use std::collections::HashMap;
use std::path::{Path, PathBuf};
const AMBIENT_LOCALE_VARS: &[&str] = &["TERM", "LANG", "LC_ALL", "TZ", "USER"];
#[cfg(windows)]
const AMBIENT_WINDOWS_VARS: &[&str] = &[
"SystemRoot",
"ComSpec",
"PATHEXT",
"SystemDrive",
"windir",
"OS",
"PROCESSOR_ARCHITECTURE",
"PSModulePath",
];
#[cfg(windows)]
pub(crate) fn extend_windows_process_env(env: &mut HashMap<String, String>) {
for key in AMBIENT_WINDOWS_VARS {
if let Some((_, value)) =
std::env::vars_os().find(|(k, _)| k.to_string_lossy().eq_ignore_ascii_case(key))
{
env.insert((*key).to_string(), value.to_string_lossy().into_owned());
}
}
}
#[cfg(windows)]
pub(crate) fn redirect_windows_profile_env(env: &mut HashMap<String, String>, base_home: &Path) {
let tmp = base_home.join("tmp");
let appdata_roaming = base_home.join("AppData").join("Roaming");
let appdata_local = base_home.join("AppData").join("Local");
for path in [&tmp, &appdata_roaming, &appdata_local] {
let _ = std::fs::create_dir_all(path);
}
env.insert("USERPROFILE".to_string(), base_home.display().to_string());
env.insert("TMPDIR".to_string(), tmp.display().to_string());
env.insert("TEMP".to_string(), tmp.display().to_string());
env.insert("TMP".to_string(), tmp.display().to_string());
env.insert("APPDATA".to_string(), appdata_roaming.display().to_string());
env.insert(
"LOCALAPPDATA".to_string(),
appdata_local.display().to_string(),
);
}
const CONTRACT_TOOLCHAIN_VARS: &[(&str, &str)] = &[
("CARGO_HOME", ".cargo"),
("RUSTUP_HOME", ".rustup"),
("NPM_CONFIG_CACHE", ".npm"),
];
const CACHE_COPY_MAX_BYTES: u64 = 64 * 1024 * 1024;
const CARGO_CACHE_NEVER_SEED: &[&str] =
&["credentials.toml", "credentials", "config.toml", "config"];
pub(crate) fn cache_only_cargo_home(base_home: &Path) -> PathBuf {
let destination = base_home.join(format!(
".cargo-cache-only-{}",
uuid::Uuid::new_v4().simple()
));
if let Err(error) = std::fs::create_dir_all(&destination) {
tracing::warn!(
path = %destination.display(),
error = %error,
"could not create cache-only Cargo home; Cargo will surface the failure"
);
return destination;
}
let Some(source) = toolchain_var_value("CARGO_HOME", ".cargo").map(PathBuf::from) else {
return destination;
};
for name in ["registry", "git"] {
let from = source.join(name);
let to = destination.join(name);
if !from.is_dir() {
continue;
}
seed_cargo_cache(name, &from, &to);
}
destination
}
fn seed_cargo_cache(name: &str, from: &Path, to: &Path) {
if crate::validator_snapshot::dir_size_exceeds(from, CACHE_COPY_MAX_BYTES) {
tracing::warn!(
cache = name,
source = %from.display(),
"shared Cargo cache exceeds the copy ceiling; LINKING it into the contract home — \
cache writes from worker-authored contract code will reach the shared cache"
);
} else if copy_cargo_cache_entries(from, to, crate::validator_snapshot::copy_dir_clonefile)
|| copy_cargo_cache_entries(from, to, crate::validator_snapshot::copy_dir_reflink)
|| copy_cargo_cache_entries(from, to, copy_entry_plain)
{
return;
} else {
tracing::warn!(
cache = name,
source = %from.display(),
"every copy tier failed for the shared Cargo cache; falling back to linking it"
);
}
link_cargo_cache(name, from, to);
}
fn copy_cargo_cache_entries(from: &Path, to: &Path, copy_entry: fn(&Path, &Path) -> bool) -> bool {
let Ok(entries) = std::fs::read_dir(from) else {
return false;
};
if std::fs::create_dir_all(to).is_err() {
return false;
}
for entry in entries.flatten() {
let file_name = entry.file_name();
if CARGO_CACHE_NEVER_SEED.contains(&file_name.to_string_lossy().as_ref()) {
tracing::warn!(
cache = %from.display(),
entry = %file_name.to_string_lossy(),
"skipping credential-shaped entry while seeding the contract Cargo cache"
);
continue;
}
if !copy_entry(&entry.path(), &to.join(&file_name)) {
let _ = std::fs::remove_dir_all(to);
return false;
}
}
true
}
fn copy_entry_plain(src: &Path, dst: &Path) -> bool {
if src.is_dir() {
crate::validator_snapshot::copy_dir_plain(src, dst).is_ok()
} else {
std::fs::copy(src, dst).is_ok()
}
}
fn link_cargo_cache(name: &str, from: &Path, to: &Path) {
#[cfg(unix)]
if let Err(error) = std::os::unix::fs::symlink(from, to) {
tracing::warn!(
cache = name,
source = %from.display(),
error = %error,
"could not seed contract Cargo cache; using an empty isolated cache"
);
}
#[cfg(windows)]
if let Err(error) = std::os::windows::fs::symlink_dir(from, to) {
tracing::warn!(
cache = name,
source = %from.display(),
error = %error,
"could not seed contract Cargo cache; using an empty isolated cache"
);
}
}
#[cfg(unix)]
pub(crate) fn os_account_home() -> Option<PathBuf> {
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut buf = vec![0_u8; 4096];
let mut entry_ptr = std::ptr::null_mut();
let rc = unsafe {
libc::getpwuid_r(
libc::getuid(),
&mut pwd,
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
&mut entry_ptr,
)
};
if rc != 0 || entry_ptr.is_null() || pwd.pw_dir.is_null() {
return None;
}
let home = unsafe { std::ffi::CStr::from_ptr(pwd.pw_dir) }
.to_string_lossy()
.into_owned();
(!home.is_empty()).then(|| PathBuf::from(home))
}
pub(crate) fn operator_home() -> Option<PathBuf> {
#[cfg(unix)]
if let Some(home) = os_account_home() {
return Some(home);
}
#[cfg(windows)]
if let Some(home) = std::env::var_os("USERPROFILE").filter(|value| !value.is_empty()) {
return Some(PathBuf::from(home));
}
std::env::var_os("HOME").map(PathBuf::from)
}
fn toolchain_var_value(var: &str, default_subdir: &str) -> Option<String> {
if let Some(value) = std::env::var_os(var) {
return Some(value.to_string_lossy().into_owned());
}
let real_home = operator_home()?;
let candidate = real_home.join(default_subdir);
candidate.is_dir().then(|| candidate.display().to_string())
}
pub(crate) fn extend_noncredential_toolchain_env(env: &mut HashMap<String, String>) {
for (var, default_subdir) in CONTRACT_TOOLCHAIN_VARS {
if *var == "CARGO_HOME" {
continue;
}
if let Some(value) = toolchain_var_value(var, default_subdir) {
env.insert((*var).to_string(), value);
}
}
}
fn managed_contract_keys() -> &'static [&'static str] {
&[
"PATH",
"HOME",
"USERPROFILE",
"TMPDIR",
"TEMP",
"TMP",
"APPDATA",
"LOCALAPPDATA",
"SystemRoot",
"SYSTEMROOT",
"ComSpec",
"COMSPEC",
"PATHEXT",
"TERM",
"LANG",
"LC_ALL",
"TZ",
"USER",
"KRANZ_BASE_SHA",
"CARGO_HOME",
"RUSTUP_HOME",
"NPM_CONFIG_CACHE",
]
}
pub fn sanitized_child_env(
base_home: &Path,
extra: &[(String, String)],
) -> HashMap<String, String> {
let _ = std::fs::create_dir_all(base_home.join("tmp"));
let mut env = HashMap::new();
if let Some(path) = std::env::var_os("PATH") {
env.insert("PATH".to_string(), path.to_string_lossy().into_owned());
}
env.insert("HOME".to_string(), base_home.display().to_string());
env.insert(
"TMPDIR".to_string(),
base_home.join("tmp").display().to_string(),
);
for key in AMBIENT_LOCALE_VARS {
if let Some(value) = std::env::var_os(key) {
env.insert((*key).to_string(), value.to_string_lossy().into_owned());
}
}
extend_noncredential_toolchain_env(&mut env);
env.insert(
"CARGO_HOME".to_string(),
cache_only_cargo_home(base_home).display().to_string(),
);
#[cfg(windows)]
{
extend_windows_process_env(&mut env);
redirect_windows_profile_env(&mut env, base_home);
}
for (key, value) in extra {
env.insert(key.clone(), value.clone());
}
env
}
pub(crate) fn probe_child_env(extra: &[(String, String)]) -> HashMap<String, String> {
let mut env = HashMap::new();
if let Some(path) = std::env::var_os("PATH") {
env.insert("PATH".to_string(), path.to_string_lossy().into_owned());
}
for key in ["HOME", "USERPROFILE"] {
if let Some(value) = std::env::var_os(key) {
env.insert(key.to_string(), value.to_string_lossy().into_owned());
}
}
for key in AMBIENT_LOCALE_VARS {
if let Some(value) = std::env::var_os(key) {
env.insert((*key).to_string(), value.to_string_lossy().into_owned());
}
}
let temp = std::env::temp_dir().display().to_string();
for key in ["TMPDIR", "TEMP", "TMP"] {
env.insert(key.to_string(), temp.clone());
}
#[cfg(windows)]
extend_windows_process_env(&mut env);
for (key, value) in extra {
env.insert(key.clone(), value.clone());
}
env
}
pub fn session_scratch_home(session_id: &str) -> PathBuf {
crate::backend_claude::scratch_home_root(session_id).join("home")
}
pub fn agent_session_env(
spec_env: &HashMap<String, String>,
session_id: &str,
auth_env_name: Option<&str>,
) -> HashMap<String, String> {
let base_home = spec_env
.get("HOME")
.map(PathBuf::from)
.unwrap_or_else(|| session_scratch_home(session_id));
session_env_with_home(spec_env, session_id, auth_env_name, &base_home)
}
pub fn session_env_with_home(
spec_env: &HashMap<String, String>,
session_id: &str,
auth_env_name: Option<&str>,
base_home: &Path,
) -> HashMap<String, String> {
let mut extra: Vec<(String, String)> = spec_env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if let Some(name) = auth_env_name {
if let Some(value) = std::env::var_os(name).filter(|v| !v.is_empty()) {
tracing::info!(
session_id = %session_id,
key = name,
"backend auth env var injected from ambient into cleared session env"
);
extra.push((name.to_string(), value.to_string_lossy().into_owned()));
}
}
sanitized_child_env(base_home, &extra)
}
pub fn contract_command_env(
mission_scratch: &Path,
base_sha: Option<&str>,
passthrough: &[String],
) -> HashMap<String, String> {
let mut extra: Vec<(String, String)> =
crate::runner::contract_env(base_sha).into_iter().collect();
for (var, default_subdir) in CONTRACT_TOOLCHAIN_VARS {
if *var == "CARGO_HOME" {
continue;
}
if let Some(value) = toolchain_var_value(var, default_subdir) {
extra.push(((*var).to_string(), value));
}
}
let managed = managed_contract_keys();
for name in passthrough {
let name = name.trim();
if name.is_empty() {
continue;
}
if managed.iter().any(|m| m.eq_ignore_ascii_case(name)) {
tracing::warn!(
key = name,
"contractEnvPassthrough entry refused: name is managed by the contract env itself"
);
continue;
}
match std::env::var_os(name) {
Some(value) => {
extra.push((name.to_string(), value.to_string_lossy().into_owned()));
}
None => {
tracing::warn!(
key = name,
"contractEnvPassthrough entry named a var that is not set in the ambient env"
);
}
}
}
sanitized_child_env(mission_scratch, &extra)
}
#[cfg(test)]
pub(crate) static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(test)]
pub(crate) fn isolated_global_home_test(name: &str) -> bool {
if std::env::var("KRANZ_ISOLATED_GLOBAL_HOME_TEST").as_deref() == Ok(name) {
return false;
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([name, "--exact", "--nocapture"])
.env("KRANZ_ISOLATED_GLOBAL_HOME_TEST", name)
.env("RUST_TEST_THREADS", "1")
.env_remove("KRANZ_HOME")
.output()
.unwrap();
assert!(
output.status.success(),
"isolated fixture {name}: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout).contains("1 passed;"),
"fixture filter matched no test"
);
true
}
#[cfg(test)]
pub(crate) struct EnvTestGuard {
vars: Vec<(&'static str, Option<std::ffi::OsString>)>,
_lock: std::sync::MutexGuard<'static, ()>,
}
#[cfg(test)]
impl EnvTestGuard {
pub(crate) fn engage(settings: &[(&'static str, &str)]) -> Self {
let lock = ENV_TEST_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let vars = settings
.iter()
.map(|(name, value)| {
let prev = std::env::var_os(name);
std::env::set_var(name, value);
(*name, prev)
})
.collect();
EnvTestGuard { vars, _lock: lock }
}
pub(crate) fn engage_unsetting(
settings: &[(&'static str, &str)],
unset: &[&'static str],
) -> Self {
let lock = ENV_TEST_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let mut vars: Vec<(&'static str, Option<std::ffi::OsString>)> = settings
.iter()
.map(|(name, value)| {
let prev = std::env::var_os(name);
std::env::set_var(name, value);
(*name, prev)
})
.collect();
for name in unset {
let prev = std::env::var_os(name);
std::env::remove_var(name);
vars.push((name, prev));
}
EnvTestGuard { vars, _lock: lock }
}
}
#[cfg(test)]
impl Drop for EnvTestGuard {
fn drop(&mut self) {
for (name, prev) in &self.vars {
match prev {
Some(value) => std::env::set_var(name, value),
None => std::env::remove_var(name),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn extra(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[test]
fn sanitized_child_env_starts_empty_and_never_inherits_secrets() {
let _poison = EnvTestGuard::engage(&[
("GH_TOKEN", "hunter2"),
("SLACK_BOT_TOKEN", "xoxb-poison"),
("AWS_SECRET_ACCESS_KEY", "aws-poison"),
]);
let home = tempfile::tempdir().unwrap();
let env = sanitized_child_env(home.path(), &extra(&[("KRANZ_BASE_SHA", "deadbeef")]));
for secret in [
"GH_TOKEN",
"SLACK_BOT_TOKEN",
"AWS_SECRET_ACCESS_KEY",
"ANTHROPIC_API_KEY",
"OPENAI_API_KEY",
"SSH_AUTH_SOCK",
"GOOGLE_APPLICATION_CREDENTIALS",
] {
assert!(!env.contains_key(secret), "child env leaked {secret}");
}
assert_eq!(
env.get("HOME").map(String::as_str),
Some(home.path().to_string_lossy().as_ref()),
"HOME must be the scratch dir, never the operator's real home"
);
assert_eq!(
env.get("TMPDIR").map(String::as_str),
Some(home.path().join("tmp").to_string_lossy().as_ref()),
"TMPDIR must be <scratch>/tmp"
);
assert!(
home.path().join("tmp").is_dir(),
"the scratch tmp dir must be created for the child"
);
assert_eq!(
env.get("KRANZ_BASE_SHA").map(String::as_str),
Some("deadbeef"),
"extra must apply verbatim"
);
if std::env::var_os("PATH").is_some() {
assert!(env.contains_key("PATH"), "PATH must cross from ambient");
}
let allowed = [
"PATH",
"HOME",
"TMPDIR",
"TERM",
"LANG",
"LC_ALL",
"TZ",
"USER",
"CARGO_HOME",
"RUSTUP_HOME",
"NPM_CONFIG_CACHE",
"KRANZ_BASE_SHA",
];
for key in env.keys() {
assert!(
allowed.contains(&key.as_str()) || cfg!(windows),
"unexpected key in child env: {key}"
);
}
}
#[cfg(windows)]
#[test]
fn sanitized_child_env_windows_redirects_profile_and_temp_to_scratch() {
if isolated_global_home_test(
"agent_env::tests::sanitized_child_env_windows_redirects_profile_and_temp_to_scratch",
) {
return;
}
let home = tempfile::tempdir().unwrap();
let operator = tempfile::tempdir().unwrap();
let operator_temp = operator.path().join("operator-temp");
let operator_tmp = operator.path().join("operator-tmp");
let operator_roaming = operator.path().join("operator-roaming");
let operator_local = operator.path().join("operator-local");
for dir in [
&operator_temp,
&operator_tmp,
&operator_roaming,
&operator_local,
] {
std::fs::create_dir_all(dir).unwrap();
}
let operator_temp = operator_temp.display().to_string();
let operator_tmp = operator_tmp.display().to_string();
let operator_roaming = operator_roaming.display().to_string();
let operator_local = operator_local.display().to_string();
let _poison = EnvTestGuard::engage(&[
("TEMP", &operator_temp),
("TMP", &operator_tmp),
("APPDATA", &operator_roaming),
("LOCALAPPDATA", &operator_local),
]);
let _relocated_temp =
tempfile::tempdir().expect("relocated temporary paths must remain usable");
let env = sanitized_child_env(home.path(), &extra(&[]));
let tmp = home.path().join("tmp").display().to_string();
assert_eq!(env.get("TEMP").map(String::as_str), Some(tmp.as_str()));
assert_eq!(env.get("TMP").map(String::as_str), Some(tmp.as_str()));
assert_eq!(
env.get("USERPROFILE").map(String::as_str),
Some(home.path().to_string_lossy().as_ref())
);
assert!(
env.get("APPDATA")
.is_some_and(|v| v.starts_with(&home.path().display().to_string())),
"APPDATA must redirect under scratch, not the operator profile"
);
assert!(
env.get("LOCALAPPDATA")
.is_some_and(|v| v.starts_with(&home.path().display().to_string())),
"LOCALAPPDATA must redirect under scratch"
);
if env.keys().any(|k| k.eq_ignore_ascii_case("systemroot")) {
assert!(
env.contains_key("SystemRoot"),
"SystemRoot must be emitted under canonical casing"
);
}
let mut lowered: Vec<String> = env.keys().map(|k| k.to_ascii_lowercase()).collect();
lowered.sort();
lowered.dedup();
assert_eq!(
lowered.len(),
env.len(),
"child env block carries duplicate-case entries: {:?}",
env.keys().collect::<Vec<_>>()
);
}
#[test]
fn agent_session_env_injects_only_the_backends_own_auth_key() {
let _poison = EnvTestGuard::engage_unsetting(
&[
("ANTHROPIC_API_KEY", "sk-ant-poison"),
("GH_TOKEN", "hunter2"),
],
&["OPENAI_API_KEY"],
);
let env = agent_session_env(&HashMap::new(), "sess-claude", Some("ANTHROPIC_API_KEY"));
assert_eq!(
env.get("ANTHROPIC_API_KEY").map(String::as_str),
Some("sk-ant-poison"),
"the backend's own auth key must be injected when set"
);
assert!(!env.contains_key("GH_TOKEN"), "GH_TOKEN never crosses");
assert_eq!(
env.get("HOME").map(String::as_str),
Some(
session_scratch_home("sess-claude")
.to_string_lossy()
.as_ref()
),
"a HOME-less spec gets the per-session scratch home"
);
let env = agent_session_env(&HashMap::new(), "sess-codex", Some("OPENAI_API_KEY"));
assert!(
!env.contains_key("ANTHROPIC_API_KEY"),
"another backend's auth key must never be injected"
);
assert!(!env.contains_key("OPENAI_API_KEY"), "not set in ambient");
}
#[test]
fn agent_session_env_honors_the_specs_relocated_home() {
let home = tempfile::tempdir().unwrap();
let mut spec_env = HashMap::new();
spec_env.insert("HOME".to_string(), home.path().display().to_string());
spec_env.insert(
"CLAUDE_CONFIG_DIR".to_string(),
home.path().join(".claude").display().to_string(),
);
let env = agent_session_env(&spec_env, "sess-worker", None);
assert_eq!(
env.get("HOME").map(String::as_str),
Some(home.path().to_string_lossy().as_ref())
);
assert_eq!(
env.get("CLAUDE_CONFIG_DIR").map(String::as_str),
Some(home.path().join(".claude").to_string_lossy().as_ref()),
"the seeded config dir must survive env clearing (auth probe shape)"
);
}
#[cfg(unix)]
#[cfg(unix)]
#[test]
fn toolchain_home_os_account_resolves_when_home_is_unset() {
let real_home = std::env::var_os("HOME").map(PathBuf::from).unwrap();
let _guard = EnvTestGuard::engage_unsetting(&[], &["HOME", "CARGO_HOME", "RUSTUP_HOME"]);
let account_home = os_account_home().expect("this host has a passwd entry");
assert_eq!(account_home, real_home, "account record == $HOME here");
assert_eq!(operator_home().as_deref(), Some(real_home.as_path()));
if real_home.join(".rustup").is_dir() {
assert_eq!(
toolchain_var_value("RUSTUP_HOME", ".rustup"),
Some(real_home.join(".rustup").display().to_string())
);
}
}
#[cfg(unix)]
#[test]
fn toolchain_home_os_account_env_var_is_still_an_explicit_override() {
let _guard = EnvTestGuard::engage(&[("RUSTUP_HOME", "/explicit/override")]);
assert_eq!(
toolchain_var_value("RUSTUP_HOME", ".rustup"),
Some("/explicit/override".to_string()),
"an explicit toolchain env var always wins"
);
}
#[test]
fn noncredential_toolchain_extension_never_carries_cargo_home() {
let _guard = EnvTestGuard::engage(&[
("CARGO_HOME", "/operator/cargo-with-credentials"),
("RUSTUP_HOME", "/operator/rustup"),
("NPM_CONFIG_CACHE", "/operator/npm-cache"),
]);
let mut env = HashMap::new();
extend_noncredential_toolchain_env(&mut env);
assert_eq!(
env.get("RUSTUP_HOME").map(String::as_str),
Some("/operator/rustup")
);
assert_eq!(
env.get("NPM_CONFIG_CACHE").map(String::as_str),
Some("/operator/npm-cache")
);
assert!(!env.contains_key("CARGO_HOME"));
}
#[cfg(unix)]
#[test]
fn toolchain_home_os_account_keeps_session_env_shape_unchanged() {
let _guard = EnvTestGuard::engage_unsetting(&[], &["CARGO_HOME", "RUSTUP_HOME"]);
let real_home = std::env::var_os("HOME").map(PathBuf::from).unwrap();
let scratch = tempfile::tempdir().unwrap();
let env = contract_command_env(scratch.path(), None, &[]);
if real_home.join(".rustup").is_dir() {
assert_eq!(
env.get("RUSTUP_HOME").map(String::as_str),
Some(real_home.join(".rustup").display().to_string().as_str()),
"RUSTUP_HOME still derives from the operator's real home"
);
}
}
#[test]
fn contract_env_derives_toolchain_homes_from_the_real_home_and_cargo_runs() {
let _guard = EnvTestGuard::engage_unsetting(&[], &["RUSTUP_HOME", "CARGO_HOME"]);
let scratch = tempfile::tempdir().unwrap();
let real_home = operator_home().expect("operator home");
let env = contract_command_env(scratch.path(), None, &[]);
let rustup_home = real_home.join(".rustup");
if rustup_home.is_dir() {
assert_eq!(
env.get("RUSTUP_HOME").map(String::as_str),
Some(rustup_home.display().to_string().as_str()),
"RUSTUP_HOME derives from the operator's real home"
);
}
let cargo_home = PathBuf::from(env.get("CARGO_HOME").expect("CARGO_HOME"));
assert!(
cargo_home.starts_with(scratch.path()),
"CARGO_HOME must be isolated under mission scratch: {}",
cargo_home.display()
);
assert_ne!(
cargo_home,
real_home.join(".cargo"),
"the operator's real Cargo home must never reach contract code"
);
let mut cmd = std::process::Command::new("cargo");
cmd.arg("--version")
.env_clear()
.envs(&env)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let out = cmd.output().expect("spawn cargo --version");
assert!(
out.status.success(),
"cargo --version must succeed under the generated env: {}",
String::from_utf8_lossy(&out.stderr)
);
let version = String::from_utf8_lossy(&out.stdout);
assert!(
version.starts_with("cargo "),
"expected a cargo version string: {version}"
);
}
#[test]
fn contract_command_env_shapes_the_gate_boundary() {
let _guard = EnvTestGuard::engage(&[
("RUSTUP_HOME", "/poisoned/rustup-home"),
("CARGO_HOME", "/poisoned/cargo-home"),
("KRANZ_AGENT_ENV_TEST_CRED", "cred-value"),
("GH_TOKEN", "hunter2"),
]);
let scratch = tempfile::tempdir().unwrap();
let env = contract_command_env(scratch.path(), Some("deadbeef"), &[]);
assert_eq!(
env.get("KRANZ_BASE_SHA").map(String::as_str),
Some("deadbeef")
);
assert_eq!(
env.get("RUSTUP_HOME").map(String::as_str),
Some("/poisoned/rustup-home"),
"toolchain caches cross from ambient"
);
let cargo_home = PathBuf::from(env.get("CARGO_HOME").expect("CARGO_HOME"));
assert!(
cargo_home.starts_with(scratch.path()),
"CARGO_HOME must be cache-only mission scratch: {}",
cargo_home.display()
);
assert_ne!(cargo_home, PathBuf::from("/poisoned/cargo-home"));
for forbidden in ["credentials.toml", "credentials", "config.toml", "config"] {
assert!(
!cargo_home.join(forbidden).exists(),
"cache-only Cargo home copied forbidden root file {forbidden}"
);
}
assert!(!env.contains_key("GH_TOKEN"));
assert!(
!env.contains_key("KRANZ_AGENT_ENV_TEST_CRED"),
"a credential crosses ONLY when named in contractEnvPassthrough"
);
assert_eq!(
env.get("HOME").map(String::as_str),
Some(scratch.path().to_string_lossy().as_ref())
);
let env = contract_command_env(
scratch.path(),
None,
&[
"KRANZ_AGENT_ENV_TEST_CRED".to_string(),
"home".to_string(),
"KRANZ_AGENT_ENV_TEST_UNSET".to_string(),
],
);
assert_eq!(
env.get("KRANZ_AGENT_ENV_TEST_CRED").map(String::as_str),
Some("cred-value"),
"the passthrough-named var crosses"
);
assert_eq!(
env.get("HOME").map(String::as_str),
Some(scratch.path().to_string_lossy().as_ref()),
"a passthrough entry naming `home` (any casing) must be refused"
);
assert!(
!env.contains_key("KRANZ_BASE_SHA"),
"no base sha pinned => no KRANZ_BASE_SHA key"
);
}
#[cfg(unix)]
#[test]
fn contract_cargo_home_contains_caches_but_no_credentials_or_config() {
let source = tempfile::tempdir().unwrap();
std::fs::create_dir_all(source.path().join("registry")).unwrap();
std::fs::create_dir_all(source.path().join("git")).unwrap();
std::fs::write(source.path().join("registry/cache-marker"), "registry").unwrap();
std::fs::write(source.path().join("git/cache-marker"), "git").unwrap();
for name in ["credentials.toml", "credentials", "config.toml", "config"] {
std::fs::write(source.path().join(name), "operator-secret").unwrap();
}
let _guard = EnvTestGuard::engage(&[(
"CARGO_HOME",
source.path().to_str().expect("utf-8 temp path"),
)]);
let scratch = tempfile::tempdir().unwrap();
let env = contract_command_env(scratch.path(), None, &[]);
let cargo_home = PathBuf::from(env.get("CARGO_HOME").expect("CARGO_HOME"));
for cache in ["registry", "git"] {
assert_eq!(
std::fs::read_to_string(cargo_home.join(cache).join("cache-marker")).unwrap(),
cache
);
}
for forbidden in ["credentials.toml", "credentials", "config.toml", "config"] {
assert!(
std::fs::symlink_metadata(cargo_home.join(forbidden)).is_err(),
"cache-only Cargo home exposed {forbidden}"
);
}
}
#[cfg(unix)]
#[test]
fn contract_cache_cow_seeds_real_copies_and_isolates_writes() {
let source = tempfile::tempdir().unwrap();
std::fs::create_dir_all(source.path().join("registry/cache")).unwrap();
std::fs::create_dir_all(source.path().join("git/db")).unwrap();
std::fs::write(source.path().join("registry/cache/crate-a.crate"), "aaaa").unwrap();
std::fs::write(source.path().join("git/db/HEAD"), "ref: refs/heads/main").unwrap();
for name in ["credentials.toml", "credentials", "config.toml", "config"] {
std::fs::write(source.path().join(name), "operator-secret").unwrap();
std::fs::write(source.path().join("registry").join(name), "planted-secret").unwrap();
}
let _guard = EnvTestGuard::engage(&[(
"CARGO_HOME",
source.path().to_str().expect("utf-8 temp path"),
)]);
let scratch = tempfile::tempdir().unwrap();
let env = contract_command_env(scratch.path(), None, &[]);
let cargo_home = PathBuf::from(env.get("CARGO_HOME").expect("CARGO_HOME"));
for cache in ["registry", "git"] {
let seeded = cargo_home.join(cache);
assert!(
!std::fs::symlink_metadata(&seeded)
.unwrap()
.file_type()
.is_symlink(),
"{cache} must be seeded as a real copy, not a symlink into the operator's cache"
);
}
assert_eq!(
std::fs::read_to_string(cargo_home.join("registry/cache/crate-a.crate")).unwrap(),
"aaaa",
"cache contents survive the seed"
);
assert_eq!(
std::fs::read_to_string(cargo_home.join("git/db/HEAD")).unwrap(),
"ref: refs/heads/main"
);
std::fs::write(cargo_home.join("registry/cache/poisoned.crate"), "x").unwrap();
std::fs::write(cargo_home.join("registry/cache/crate-a.crate"), "POISON").unwrap();
assert!(
!source.path().join("registry/cache/poisoned.crate").exists(),
"a new file written through the seeded cache must not reach the operator's cache"
);
assert_eq!(
std::fs::read_to_string(source.path().join("registry/cache/crate-a.crate")).unwrap(),
"aaaa",
"an overwrite through the seeded cache must not reach the operator's cache"
);
for forbidden in ["credentials.toml", "credentials", "config.toml", "config"] {
assert!(
std::fs::symlink_metadata(cargo_home.join(forbidden)).is_err(),
"cache-only Cargo home exposed {forbidden}"
);
assert!(
std::fs::symlink_metadata(cargo_home.join("registry").join(forbidden)).is_err(),
"the copy tier smuggled a planted {forbidden} out of the cache dir"
);
}
}
#[test]
fn contract_cache_cow_links_only_above_the_copy_ceiling() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("a.bin"), vec![0u8; 8]).unwrap();
std::fs::create_dir_all(dir.path().join("nested")).unwrap();
std::fs::write(dir.path().join("nested/b.bin"), vec![0u8; 8]).unwrap();
let probe = crate::validator_snapshot::dir_size_exceeds;
assert!(!probe(dir.path(), 16), "exactly at the limit: copies");
assert!(probe(dir.path(), 15), "one byte over: links");
assert!(probe(dir.path(), 0));
assert!(
!probe(dir.path(), CACHE_COPY_MAX_BYTES),
"a small cache is always copied"
);
assert_eq!(CACHE_COPY_MAX_BYTES, 64 * 1024 * 1024);
}
}