vtcode_core/tools/validation/
unified_path.rs1use anyhow::{Result, bail};
2use std::path::{Path, PathBuf};
3
4pub async fn validate_and_resolve_path(workspace_root: &Path, path_str: &str) -> Result<PathBuf> {
11 super::paths::validate_path_safety(path_str)?;
13
14 let path = Path::new(path_str);
16 let absolute = if path.is_absolute() {
17 path.to_path_buf()
18 } else {
19 workspace_root.join(path)
20 };
21 let normalized = crate::utils::path::normalize_path(&absolute);
22 let normalized_root = crate::utils::path::normalize_path(workspace_root);
23
24 if !normalized.starts_with(&normalized_root) {
25 bail!(
26 "Path '{}' resolves outside the workspace boundary",
27 path_str
28 );
29 }
30
31 let canonical = crate::utils::path::canonicalize_allow_missing(&normalized).await?;
33 let canonical_root = if tokio::fs::try_exists(workspace_root).await.unwrap_or(false) {
34 tokio::fs::canonicalize(workspace_root)
35 .await
36 .unwrap_or_else(|_| normalized_root.clone())
37 } else {
38 normalized_root
39 };
40
41 if !canonical.starts_with(&canonical_root) {
42 bail!(
43 "Path '{}' resolves outside the workspace boundary (after symlink resolution)",
44 path_str
45 );
46 }
47
48 Ok(canonical)
49}