metrale_bench/benchmarks/agentic/
agent_path.rs1use std::path::{Component, Path, PathBuf};
9
10use anyhow::{Result, bail};
11
12pub fn resolve(sandbox: &Path, path: &str) -> Result<PathBuf> {
20 let path = Path::new(path);
21 let path = match path.strip_prefix(sandbox) {
22 Ok(inside) => inside,
23 Err(_) if path.is_absolute() => bail!(
24 "path must be inside the project directory {}: {}",
25 sandbox.display(),
26 path.display()
27 ),
28 Err(_) => path,
29 };
30 let mut out = sandbox.to_path_buf();
31 for component in path.components() {
32 match component {
33 Component::Normal(c) => out.push(c),
34 Component::CurDir => {}
35 Component::ParentDir => bail!("path must not leave the project directory"),
36 Component::RootDir | Component::Prefix(_) => bail!("absolute paths are not allowed"),
37 }
38 }
39 if leaves_via_symlink(sandbox, &out) {
40 bail!("path must not leave the project directory through a symlink");
41 }
42 Ok(out)
43}
44
45fn leaves_via_symlink(sandbox: &Path, out: &Path) -> bool {
49 let Ok(root) = std::fs::canonicalize(sandbox) else {
50 return false;
51 };
52 deepest_existing_real(out, 40).is_none_or(|real| !real.starts_with(&root))
53}
54
55fn deepest_existing_real(path: &Path, hops: usize) -> Option<PathBuf> {
58 let mut probe = path;
59 loop {
60 if let Ok(real) = std::fs::canonicalize(probe) {
61 return Some(real);
62 }
63 if std::fs::symlink_metadata(probe).is_ok_and(|meta| meta.file_type().is_symlink()) {
64 if hops == 0 {
65 return None;
66 }
67 let target = std::fs::read_link(probe).ok()?;
68 let target = match target.is_absolute() {
69 true => target,
70 false => probe.parent()?.join(target),
71 };
72 return deepest_existing_real(&target, hops - 1);
73 }
74 probe = probe.parent()?;
75 }
76}