Skip to main content

vtcode_core/tools/validation/
paths.rs

1use anyhow::{Result, anyhow};
2use vtcode_commons::paths::validate_path_safety as common_validate_path_safety;
3
4/// Validates that a path is safe to use.
5/// Re-exported from vtcode-commons for tool-specific use cases.
6pub fn validate_path_safety(path: &str) -> Result<()> {
7    common_validate_path_safety(path)
8}
9
10/// Validates that a directory-listing request targets a safe path.
11/// Blocks empty paths, absent paths, and absolute filesystem root `/`,
12/// but allows `.` (workspace root) — loop detection and `max_items`
13/// caps handle the "infinite loops" concern that originally motivated
14/// blocking root-listing.
15pub fn validate_non_root_listing_path(path: Option<&str>) -> Result<()> {
16    let raw = path.unwrap_or_default();
17    let normalized = raw.trim_start_matches("./").trim_start_matches('/');
18    if normalized.is_empty() && raw != "." && raw != "./" {
19        return Err(anyhow!(
20            "Error: directory-listing path is empty. Please specify a subdirectory like 'src/', 'vtcode-core/src/', or 'tests/'."
21        ));
22    }
23
24    Ok(())
25}
26
27#[cfg(test)]
28mod tests {
29    use super::{validate_non_root_listing_path, validate_path_safety};
30
31    #[test]
32    fn allows_macos_temp_paths_under_var_folders() {
33        validate_path_safety("/var/folders/ab/cd/tmp123/file.txt").unwrap();
34    }
35
36    #[test]
37    fn still_blocks_sensitive_var_paths() {
38        assert!(validate_path_safety("/var/db/shadow").is_err());
39        assert!(validate_path_safety("/var").is_err());
40    }
41
42    #[test]
43    fn allows_non_critical_prefix_matches() {
44        validate_path_safety("/varnish/cache/file").unwrap();
45    }
46
47    #[test]
48    fn allows_var_tmp_paths() {
49        validate_path_safety("/var/tmp/vtcode/run.log").unwrap();
50    }
51
52    #[test]
53    fn blocks_empty_or_missing_paths() {
54        for candidate in [None, Some(""), Some("/")] {
55            assert!(
56                validate_non_root_listing_path(candidate).is_err(),
57                "should block {:?}",
58                candidate
59            );
60        }
61    }
62
63    #[test]
64    fn allows_root_listing_path() {
65        for candidate in [Some("."), Some("./")] {
66            validate_non_root_listing_path(candidate)
67                .unwrap_or_else(|_| panic!("should allow {:?}", candidate));
68        }
69    }
70
71    #[test]
72    fn allows_subdirectory_listing_requests() {
73        for candidate in [Some("src"), Some("./src"), Some("/workspace/src")] {
74            validate_non_root_listing_path(candidate).unwrap();
75        }
76    }
77}