runner_core/
path_safety.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4
5/// Normalize a user-supplied path and ensure it stays within an allowed root.
6/// Rejects absolute candidates and any that escape the root via `..`.
7pub fn normalize_under_root(root: &Path, candidate: &Path) -> Result<PathBuf> {
8    if candidate.is_absolute() {
9        anyhow::bail!("absolute paths are not allowed: {}", candidate.display());
10    }
11
12    let joined = root.join(candidate);
13    let canon = joined
14        .canonicalize()
15        .with_context(|| format!("failed to canonicalize {}", joined.display()))?;
16
17    if !canon.starts_with(root) {
18        anyhow::bail!(
19            "path escapes root ({}): {}",
20            root.display(),
21            canon.display()
22        );
23    }
24
25    Ok(canon)
26}