Skip to main content

ai_agent/utils/permissions/
path_validation.rs

1// Source: ~/claudecode/openclaudecode/src/utils/permissions/pathValidation.ts
2#![allow(dead_code)]
3
4//! Path validation utilities for file system permission checks.
5//!
6//! Handles tilde expansion, glob pattern validation, and path resolution.
7
8use super::filesystem::{
9    FileOperationType, PathCheckResult, contains_path_traversal, expand_tilde,
10    get_glob_base_directory, is_path_allowed, to_posix_path,
11};
12use crate::types::permissions::{PermissionDecisionReason, ToolPermissionContext};
13use std::path::{MAIN_SEPARATOR, Path};
14
15/// Maximum number of directories to list in error messages.
16const MAX_DIRS_TO_LIST: usize = 5;
17
18/// Regex for glob pattern characters.
19const GLOB_PATTERN_CHARS: &str = "*?[]{}";
20
21/// Checks if a path contains glob pattern characters.
22fn has_glob_pattern(path: &str) -> bool {
23    path.chars().any(|c| GLOB_PATTERN_CHARS.contains(c))
24}
25
26/// Checks if a resolved path is in the sandbox write allowlist.
27pub fn is_path_in_sandbox_write_allowlist(_resolved_path: &str) -> bool {
28    // Simplified — full implementation requires sandbox integration
29    false
30}
31
32/// Validates a glob pattern by checking its base directory.
33pub fn validate_glob_pattern(
34    clean_path: &str,
35    cwd: &str,
36    tool_permission_context: &ToolPermissionContext,
37    operation_type: FileOperationType,
38) -> ResolvedPathCheckResult {
39    if contains_path_traversal(clean_path) {
40        let absolute_path = if Path::new(clean_path).is_absolute() {
41            clean_path.to_string()
42        } else {
43            format!("{}/{}", cwd, clean_path)
44        };
45        let resolved_path = absolute_path.clone();
46        let result = is_path_allowed(
47            &resolved_path,
48            tool_permission_context,
49            operation_type,
50            None,
51        );
52        return ResolvedPathCheckResult {
53            allowed: result.allowed,
54            resolved_path,
55            decision_reason: result.decision_reason,
56        };
57    }
58
59    let base_path = get_glob_base_directory(clean_path);
60    let absolute_base_path = if Path::new(&base_path).is_absolute() {
61        base_path
62    } else {
63        format!("{}/{}", cwd, base_path)
64    };
65    let resolved_path = absolute_base_path.clone();
66    let result = is_path_allowed(
67        &resolved_path,
68        tool_permission_context,
69        operation_type,
70        None,
71    );
72    ResolvedPathCheckResult {
73        allowed: result.allowed,
74        resolved_path,
75        decision_reason: result.decision_reason,
76    }
77}
78
79/// Validates a file system path, handling tilde expansion and glob patterns.
80pub fn validate_path(
81    path: &str,
82    cwd: &str,
83    tool_permission_context: &ToolPermissionContext,
84    operation_type: FileOperationType,
85) -> ResolvedPathCheckResult {
86    // Remove surrounding quotes if present
87    let clean_path = expand_tilde(
88        path.trim_start_matches(['\'', '"'])
89            .trim_end_matches(['\'', '"']),
90    );
91
92    // Block UNC paths
93    if contains_vulnerable_unc_path(&clean_path) {
94        return ResolvedPathCheckResult {
95            allowed: false,
96            resolved_path: clean_path.clone(),
97            decision_reason: Some(PermissionDecisionReason::Other {
98                reason: "UNC network paths require manual approval".to_string(),
99            }),
100        };
101    }
102
103    // Reject tilde variants (~user, ~+, ~-, ~N)
104    if clean_path.starts_with('~') {
105        return ResolvedPathCheckResult {
106            allowed: false,
107            resolved_path: clean_path.clone(),
108            decision_reason: Some(PermissionDecisionReason::Other {
109                reason: "Tilde expansion variants (~user, ~+, ~-) in paths require manual approval"
110                    .to_string(),
111            }),
112        };
113    }
114
115    // Reject shell expansion syntax
116    if clean_path.contains('$') || clean_path.contains('%') || clean_path.starts_with('=') {
117        return ResolvedPathCheckResult {
118            allowed: false,
119            resolved_path: clean_path.clone(),
120            decision_reason: Some(PermissionDecisionReason::Other {
121                reason: "Shell expansion syntax in paths requires manual approval".to_string(),
122            }),
123        };
124    }
125
126    // Handle glob patterns
127    if has_glob_pattern(&clean_path) {
128        if matches!(
129            operation_type,
130            FileOperationType::Write | FileOperationType::Create
131        ) {
132            return ResolvedPathCheckResult {
133                allowed: false,
134                resolved_path: clean_path.clone(),
135                decision_reason: Some(PermissionDecisionReason::Other {
136                    reason: "Glob patterns are not allowed in write operations. Please specify an exact file path.".to_string(),
137                }),
138            };
139        }
140        return validate_glob_pattern(&clean_path, cwd, tool_permission_context, operation_type);
141    }
142
143    // Resolve path
144    let absolute_path = if Path::new(&clean_path).is_absolute() {
145        clean_path
146    } else {
147        format!("{}/{}", cwd, clean_path)
148    };
149    let resolved_path = absolute_path.clone();
150
151    let result = is_path_allowed(
152        &resolved_path,
153        tool_permission_context,
154        operation_type,
155        None,
156    );
157    ResolvedPathCheckResult {
158        allowed: result.allowed,
159        resolved_path,
160        decision_reason: result.decision_reason,
161    }
162}
163
164/// Checks if a path contains a vulnerable UNC pattern.
165pub fn contains_vulnerable_unc_path(path: &str) -> bool {
166    // Simplified UNC detection
167    path.starts_with("\\\\") && !path.starts_with("\\\\?\\")
168        || path.starts_with("//") && !path.starts_with("//?/")
169}
170
171/// Resolved path check result.
172pub struct ResolvedPathCheckResult {
173    pub allowed: bool,
174    pub resolved_path: String,
175    pub decision_reason: Option<PermissionDecisionReason>,
176}