use super::super::process_cwd::enforce_process_cwd_for_policy;
use super::super::*;
use crate::orchestration::{pop_execution_policy, push_execution_policy};
#[test]
fn a_process_only_root_is_launchable_without_becoming_writable() {
let dir = tempfile::tempdir().unwrap();
let workspace = dir.path().join("workspace");
let elsewhere = dir.path().join("elsewhere");
std::fs::create_dir_all(&workspace).unwrap();
std::fs::create_dir_all(&elsewhere).unwrap();
let policy = CapabilityPolicy {
workspace_roots: vec![workspace.display().to_string()],
process_sandbox: crate::orchestration::ProcessSandboxPolicy {
read_roots: vec![elsewhere.display().to_string()],
..Default::default()
},
sandbox_profile: SandboxProfile::Worktree,
..CapabilityPolicy::default()
};
enforce_process_cwd_for_policy(&elsewhere, &policy)
.expect("a process-only root is somewhere a subprocess may start");
push_execution_policy(policy);
let write = check_fs_path_scope(&elsewhere.join("out.txt"), FsAccess::Write);
pop_execution_policy();
assert!(
write.is_err(),
"granting a root on the process axis must not let file builtins write there"
);
}
#[test]
fn a_read_only_root_is_launchable_without_becoming_writable() {
let dir = tempfile::tempdir().unwrap();
let workspace = dir.path().join("workspace");
let read_only = dir.path().join("persona");
std::fs::create_dir_all(&workspace).unwrap();
std::fs::create_dir_all(&read_only).unwrap();
let policy = CapabilityPolicy {
workspace_roots: vec![workspace.display().to_string()],
read_only_roots: vec![read_only.display().to_string()],
sandbox_profile: SandboxProfile::Worktree,
..CapabilityPolicy::default()
};
enforce_process_cwd_for_policy(&read_only, &policy)
.expect("a read-only root is somewhere a subprocess may start");
push_execution_policy(policy);
let read = check_fs_path_scope(&read_only.join("persona.md"), FsAccess::Read);
let write = check_fs_path_scope(&read_only.join("out.txt"), FsAccess::Write);
pop_execution_policy();
assert!(read.is_ok(), "file builtins retain declared read authority");
assert!(
write.is_err(),
"making a read-only root launchable must not make it writable"
);
}
#[test]
fn a_root_on_neither_axis_is_not_launchable() {
let dir = tempfile::tempdir().unwrap();
let workspace = dir.path().join("workspace");
let outside = dir.path().join("outside");
std::fs::create_dir_all(&workspace).unwrap();
std::fs::create_dir_all(&outside).unwrap();
let policy = CapabilityPolicy {
workspace_roots: vec![workspace.display().to_string()],
sandbox_profile: SandboxProfile::Worktree,
..CapabilityPolicy::default()
};
let error = enforce_process_cwd_for_policy(&outside, &policy)
.expect_err("an ungranted directory is not a legal cwd");
assert!(
error.to_string().contains("process cwd"),
"the diagnostic must say which axis rejected the launch: {error}"
);
}
#[test]
fn empty_workspace_roots_default_to_execution_root_for_process_cwd() {
let _env_lock = crate::runtime_paths::test_env_lock()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
std::env::remove_var("HARN_PROJECT_ROOT");
let dir = tempfile::tempdir().unwrap();
crate::stdlib::process::set_thread_execution_context(Some(
crate::orchestration::RunExecutionRecord {
cwd: Some(dir.path().to_string_lossy().into_owned()),
..Default::default()
},
));
push_execution_policy(CapabilityPolicy {
sandbox_profile: SandboxProfile::Worktree,
..CapabilityPolicy::default()
});
assert!(enforce_process_cwd(dir.path()).is_ok());
let outside = tempfile::tempdir().unwrap();
assert!(enforce_process_cwd(outside.path()).is_err());
pop_execution_policy();
crate::stdlib::process::set_thread_execution_context(None);
}
#[test]
fn std_command_for_resolves_a_bare_program_name_to_an_absolute_path() {
let _env_lock = crate::runtime_paths::test_env_lock()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let dir = tempfile::tempdir().unwrap();
let program_path = dir.path().join("myfakeprogram");
std::fs::write(&program_path, b"").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&program_path, std::fs::Permissions::from_mode(0o755)).unwrap();
}
let original_path = std::env::var_os("PATH");
std::env::set_var("PATH", dir.path());
let command = super::super::std_command_for("myfakeprogram", &[]);
match original_path {
Some(value) => std::env::set_var("PATH", value),
None => std::env::remove_var("PATH"),
}
let command = command.unwrap();
assert_eq!(command.get_program(), program_path.as_os_str());
}
#[test]
fn std_command_for_resolves_via_the_parent_path_even_when_the_isolated_child_env_has_none() {
let _env_lock = crate::runtime_paths::test_env_lock()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let dir = tempfile::tempdir().unwrap();
let program_path = dir.path().join("myfakeprogram2");
std::fs::write(&program_path, b"").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&program_path, std::fs::Permissions::from_mode(0o755)).unwrap();
}
let original_path = std::env::var_os("PATH");
std::env::set_var("PATH", dir.path());
let isolated = crate::security::SessionEnvironment::launch_from_snapshot(
crate::security::EnvironmentPolicyKind::Isolated,
Vec::new(),
std::collections::BTreeMap::new(), &|_| None,
)
.unwrap();
crate::stdlib::process::set_session_environment(Some(isolated));
let command = super::super::std_command_for("myfakeprogram2", &[]);
crate::stdlib::process::set_session_environment(None);
match original_path {
Some(value) => std::env::set_var("PATH", value),
None => std::env::remove_var("PATH"),
}
let command = command.unwrap();
assert_eq!(command.get_program(), program_path.as_os_str());
}
#[test]
fn scoped_process_sandbox_roots_concretize_empty_policy_for_command_cwd() {
let _env_lock = crate::runtime_paths::test_env_lock()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
std::env::remove_var("HARN_PROJECT_ROOT");
let execution_root = tempfile::tempdir().unwrap();
let command_root = tempfile::tempdir().unwrap();
crate::stdlib::process::set_thread_execution_context(Some(
crate::orchestration::RunExecutionRecord {
cwd: Some(execution_root.path().to_string_lossy().into_owned()),
..Default::default()
},
));
push_execution_policy(CapabilityPolicy {
sandbox_profile: SandboxProfile::Worktree,
..CapabilityPolicy::default()
});
assert!(
enforce_process_cwd(command_root.path()).is_err(),
"before the scoped overlay the command temp root is outside the execution-root fallback",
);
{
let _guard = push_process_sandbox_scope(ProcessSandboxScope {
workspace_roots: vec![command_root.path().to_string_lossy().into_owned()],
})
.unwrap();
assert!(
enforce_process_cwd(command_root.path()).is_ok(),
"scoped command root must be usable as the process cwd"
);
assert!(
enforce_process_cwd(execution_root.path()).is_err(),
"the scoped root must narrow the concrete spawn jail instead of widening it"
);
}
assert!(
enforce_process_cwd(command_root.path()).is_err(),
"the scoped command root must pop after the command spawn"
);
pop_execution_policy();
crate::stdlib::process::set_thread_execution_context(None);
}