ai_agent/utils/permissions/
path_validation.rs1#![allow(dead_code)]
3
4use 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
15const MAX_DIRS_TO_LIST: usize = 5;
17
18const GLOB_PATTERN_CHARS: &str = "*?[]{}";
20
21fn has_glob_pattern(path: &str) -> bool {
23 path.chars().any(|c| GLOB_PATTERN_CHARS.contains(c))
24}
25
26pub fn is_path_in_sandbox_write_allowlist(_resolved_path: &str) -> bool {
28 false
30}
31
32pub 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
79pub fn validate_path(
81 path: &str,
82 cwd: &str,
83 tool_permission_context: &ToolPermissionContext,
84 operation_type: FileOperationType,
85) -> ResolvedPathCheckResult {
86 let clean_path = expand_tilde(
88 path.trim_start_matches(['\'', '"'])
89 .trim_end_matches(['\'', '"']),
90 );
91
92 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 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 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 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 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
164pub fn contains_vulnerable_unc_path(path: &str) -> bool {
166 path.starts_with("\\\\") && !path.starts_with("\\\\?\\")
168 || path.starts_with("//") && !path.starts_with("//?/")
169}
170
171pub struct ResolvedPathCheckResult {
173 pub allowed: bool,
174 pub resolved_path: String,
175 pub decision_reason: Option<PermissionDecisionReason>,
176}