use std::path::{Path, PathBuf};
use super::{
normalize_for_policy, normalized_process_roots, normalized_read_only_roots,
normalized_workspace_roots, path_is_within, sandbox_rejection,
};
use crate::orchestration::CapabilityPolicy;
use crate::value::VmError;
fn process_cwd_roots(policy: &CapabilityPolicy) -> Vec<PathBuf> {
let mut roots = normalized_workspace_roots(policy);
for root in normalized_read_only_roots(policy)
.into_iter()
.chain(normalized_process_roots(&policy.process_sandbox.read_roots))
.chain(normalized_process_roots(
&policy.process_sandbox.write_roots,
))
{
if !roots.contains(&root) {
roots.push(root);
}
}
roots
}
pub(super) fn enforce_process_cwd_for_policy(
path: &Path,
policy: &CapabilityPolicy,
) -> Result<(), VmError> {
if !policy.sandbox_profile.enforces_path_scope() {
return Ok(());
}
let candidate = normalize_for_policy(path);
let roots = process_cwd_roots(policy);
if roots.iter().any(|root| path_is_within(&candidate, root)) {
return Ok(());
}
Err(sandbox_rejection(format!(
"sandbox violation: process cwd '{}' is outside the launchable roots [{}]",
candidate.display(),
roots
.iter()
.map(|root| root.display().to_string())
.collect::<Vec<_>>()
.join(", ")
)))
}
pub(crate) fn policy_process_cwd(
policy: &CapabilityPolicy,
preferred: Option<&Path>,
) -> Result<PathBuf, VmError> {
if let Some(preferred) = preferred {
let preferred = normalize_for_policy(preferred);
if enforce_process_cwd_for_policy(&preferred, policy).is_ok() {
return Ok(preferred);
}
}
let roots = normalized_workspace_roots(policy);
let current = std::env::current_dir().map_err(|error| {
VmError::Thrown(crate::value::VmValue::String(arcstr::ArcStr::from(
format!("process cwd resolution failed: {error}"),
)))
})?;
let current = normalize_for_policy(¤t);
if roots.iter().any(|root| path_is_within(¤t, root)) {
return Ok(current);
}
roots.first().cloned().ok_or_else(|| {
VmError::Thrown(crate::value::VmValue::String(arcstr::ArcStr::from(
"process cwd resolution failed: no workspace root available",
)))
})
}