@@ -11,10 +11,7 @@ use eyre::{
1111 Result ,
1212 bail,
1313} ;
14- use globset:: {
15- Glob ,
16- GlobSetBuilder ,
17- } ;
14+ use globset:: GlobSetBuilder ;
1815use serde:: {
1916 Deserialize ,
2017 Serialize ,
@@ -48,6 +45,7 @@ use crate::cli::chat::{
4845 sanitize_unicode_tags,
4946} ;
5047use crate :: os:: Os ;
48+ use crate :: util:: directories;
5149
5250#[ derive( Debug , Clone , Deserialize ) ]
5351pub struct FsRead {
@@ -102,13 +100,7 @@ impl FsRead {
102100 }
103101 }
104102
105- pub fn allowable_field_to_be_overridden ( settings : & serde_json:: Value ) -> Option < String > {
106- settings
107- . get ( "allowedPaths" )
108- . map ( |value| format ! ( "allowedPaths: {}" , value) )
109- }
110-
111- pub fn eval_perm ( & self , agent : & Agent ) -> PermissionEvalResult {
103+ pub fn eval_perm ( & self , os : & Os , agent : & Agent ) -> PermissionEvalResult {
112104 #[ derive( Debug , Deserialize ) ]
113105 #[ serde( rename_all = "camelCase" ) ]
114106 struct Settings {
@@ -141,14 +133,11 @@ impl FsRead {
141133 let allow_set = {
142134 let mut builder = GlobSetBuilder :: new ( ) ;
143135 for path in & allowed_paths {
144- let path = path. strip_suffix ( '/' ) . unwrap_or ( path. as_str ( ) ) ;
145- let Ok ( path) = shellexpand:: full ( path) else {
136+ let Ok ( path) = directories:: canonicalizes_path ( os, path) else {
146137 continue ;
147138 } ;
148- if let Ok ( glob) = Glob :: new ( path. as_ref ( ) as & str ) {
149- builder. add ( glob) ;
150- } else {
151- warn ! ( "Failed to create glob from path given: {path}. Ignoring." ) ;
139+ if let Err ( e) = directories:: add_gitignore_globs ( & mut builder, path. as_str ( ) ) {
140+ warn ! ( "Failed to create glob from path given: {path}: {e}. Ignoring." ) ;
152141 }
153142 }
154143 builder. build ( )
@@ -158,15 +147,17 @@ impl FsRead {
158147 let deny_set = {
159148 let mut builder = GlobSetBuilder :: new ( ) ;
160149 for path in & denied_paths {
161- let processed_path = path. strip_suffix ( '/' ) . unwrap_or ( path. as_str ( ) ) ;
162- let Ok ( processed_path) = shellexpand:: full ( processed_path) else {
150+ let Ok ( processed_path) = directories:: canonicalizes_path ( os, path) else {
163151 continue ;
164152 } ;
165- if let Ok ( glob) = Glob :: new ( processed_path. as_ref ( ) as & str ) {
166- sanitized_deny_list. push ( path) ;
167- builder. add ( glob) ;
168- } else {
169- warn ! ( "Failed to create glob from path given: {path}. Ignoring." ) ;
153+ match directories:: add_gitignore_globs ( & mut builder, processed_path. as_str ( ) ) {
154+ Ok ( _) => {
155+ // Note that we need to push twice here because for each rule we
156+ // are creating two globs (one for file and one for directory)
157+ sanitized_deny_list. push ( path) ;
158+ sanitized_deny_list. push ( path) ;
159+ } ,
160+ Err ( e) => warn ! ( "Failed to create glob from path given: {path}: {e}. Ignoring." ) ,
170161 }
171162 }
172163 builder. build ( )
@@ -182,8 +173,7 @@ impl FsRead {
182173 FsReadOperation :: Line ( FsLine { path, .. } )
183174 | FsReadOperation :: Directory ( FsDirectory { path, .. } )
184175 | FsReadOperation :: Search ( FsSearch { path, .. } ) => {
185- let path = path. strip_suffix ( '/' ) . unwrap_or ( path. as_str ( ) ) ;
186- let Ok ( path) = shellexpand:: full ( path) else {
176+ let Ok ( path) = directories:: canonicalizes_path ( os, path) else {
187177 ask = true ;
188178 continue ;
189179 } ;
@@ -213,8 +203,7 @@ impl FsRead {
213203 let denied_match_set = paths
214204 . iter ( )
215205 . flat_map ( |path| {
216- let path = path. strip_suffix ( '/' ) . unwrap_or ( path. as_str ( ) ) ;
217- let Ok ( path) = shellexpand:: full ( path) else {
206+ let Ok ( path) = directories:: canonicalizes_path ( os, path) else {
218207 return vec ! [ ] ;
219208 } ;
220209 deny_set. matches ( path. as_ref ( ) as & str )
@@ -1406,12 +1395,10 @@ mod tests {
14061395 ) ;
14071396 }
14081397
1409- #[ test]
1410- fn test_eval_perm ( ) {
1411- const DENIED_PATH_ONE : & str = "/some/denied/path" ;
1412- const DENIED_PATH_GLOB : & str = "/denied/glob/**/path" ;
1413- const ALLOW_PATH_ONE : & str = "/some/allow/path/**" ;
1414- const ALLOW_PATH_GLOB : & str = "/allowed/glob/**/path/**" ;
1398+ #[ tokio:: test]
1399+ async fn test_eval_perm ( ) {
1400+ const DENIED_PATH_OR_FILE : & str = "/some/denied/path" ;
1401+ const DENIED_PATH_OR_FILE_GLOB : & str = "/denied/glob/**/path" ;
14151402
14161403 let mut agent = Agent {
14171404 name : "test_agent" . to_string ( ) ,
@@ -1420,44 +1407,43 @@ mod tests {
14201407 map. insert (
14211408 ToolSettingTarget ( "fs_read" . to_string ( ) ) ,
14221409 serde_json:: json!( {
1423- "allowedPaths" : [ ALLOW_PATH_ONE , ALLOW_PATH_GLOB ] ,
1424- "deniedPaths" : [ DENIED_PATH_ONE , DENIED_PATH_GLOB ]
1410+ "deniedPaths" : [ DENIED_PATH_OR_FILE , DENIED_PATH_OR_FILE_GLOB ]
14251411 } ) ,
14261412 ) ;
14271413 map
14281414 } ,
14291415 ..Default :: default ( )
14301416 } ;
14311417
1418+ let os = Os :: new ( ) . await . unwrap ( ) ;
1419+
14321420 let tool_one = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
14331421 "operations" : [
1434- { "path" : DENIED_PATH_ONE , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1435- { "path" : format!( "{DENIED_PATH_ONE}/" ) , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1436- { "path" : "/denied/glob" , "mode" : "Directory" } ,
1437- { "path" : "/denied/glob/child_one/path" , "mode" : "Directory" } ,
1438- { "path" : "/denied/glob/child_one/grand_child_one/path" , "mode" : "Directory" } ,
1439- { "path" : TEST_FILE_PATH , "mode" : "Search" , "pattern" : "hello" }
1422+ { "path" : DENIED_PATH_OR_FILE , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1423+ { "path" : format!( "{DENIED_PATH_OR_FILE}/child" ) , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1424+ { "path" : "/denied/glob/middle_one/middle_two/path" , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1425+ { "path" : "/denied/glob/middle_one/middle_two/path/child" , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
14401426 ] ,
14411427 } ) )
14421428 . unwrap ( ) ;
14431429
1444- let res = tool_one. eval_perm ( & agent) ;
1430+ let res = tool_one. eval_perm ( & os , & agent) ;
14451431 assert ! ( matches!(
14461432 res,
14471433 PermissionEvalResult :: Deny ( ref deny_list)
1448- if deny_list. iter( ) . filter( |p| * p == DENIED_PATH_GLOB ) . collect:: <Vec <_>>( ) . len( ) == 2
1449- && deny_list. iter( ) . filter( |p| * p == DENIED_PATH_ONE ) . collect:: <Vec <_>>( ) . len( ) == 2
1434+ if deny_list. iter( ) . filter( |p| * p == DENIED_PATH_OR_FILE_GLOB ) . collect:: <Vec <_>>( ) . len( ) == 2
1435+ && deny_list. iter( ) . filter( |p| * p == DENIED_PATH_OR_FILE ) . collect:: <Vec <_>>( ) . len( ) == 2
14501436 ) ) ;
14511437
14521438 agent. allowed_tools . insert ( "fs_read" . to_string ( ) ) ;
14531439
14541440 // Denied set should remain denied
1455- let res = tool_one. eval_perm ( & agent) ;
1441+ let res = tool_one. eval_perm ( & os , & agent) ;
14561442 assert ! ( matches!(
14571443 res,
14581444 PermissionEvalResult :: Deny ( ref deny_list)
1459- if deny_list. iter( ) . filter( |p| * p == DENIED_PATH_GLOB ) . collect:: <Vec <_>>( ) . len( ) == 2
1460- && deny_list. iter( ) . filter( |p| * p == DENIED_PATH_ONE ) . collect:: <Vec <_>>( ) . len( ) == 2
1445+ if deny_list. iter( ) . filter( |p| * p == DENIED_PATH_OR_FILE_GLOB ) . collect:: <Vec <_>>( ) . len( ) == 2
1446+ && deny_list. iter( ) . filter( |p| * p == DENIED_PATH_OR_FILE ) . collect:: <Vec <_>>( ) . len( ) == 2
14611447 ) ) ;
14621448 }
14631449}
0 commit comments