@@ -144,10 +144,12 @@ impl FsRead {
144144 builder. build ( )
145145 } ;
146146
147+ let mut sanitized_deny_list = Vec :: < & String > :: new ( ) ;
147148 let deny_set = {
148149 let mut builder = GlobSetBuilder :: new ( ) ;
149150 for path in & denied_paths {
150151 if let Ok ( glob) = Glob :: new ( path) {
152+ sanitized_deny_list. push ( path) ;
151153 builder. add ( glob) ;
152154 } else {
153155 warn ! ( "Failed to create glob from path given: {path}. Ignoring." ) ;
@@ -158,43 +160,66 @@ impl FsRead {
158160
159161 match ( allow_set, deny_set) {
160162 ( Ok ( allow_set) , Ok ( deny_set) ) => {
161- let eval_res = self
162- . operations
163- . iter ( )
164- . map ( |op| {
165- match op {
166- FsReadOperation :: Line ( FsLine { path, .. } )
167- | FsReadOperation :: Directory ( FsDirectory { path, .. } )
168- | FsReadOperation :: Search ( FsSearch { path, .. } ) => {
169- if deny_set. is_match ( path) {
170- return PermissionEvalResult :: Deny ;
171- }
172- if allow_set. is_match ( path) {
173- return PermissionEvalResult :: Allow ;
174- }
175- } ,
176- FsReadOperation :: Image ( fs_image) => {
177- let paths = & fs_image. image_paths ;
178- if paths. iter ( ) . any ( |path| deny_set. is_match ( path) ) {
179- return PermissionEvalResult :: Deny ;
180- }
181- if paths. iter ( ) . all ( |path| allow_set. is_match ( path) ) {
182- return PermissionEvalResult :: Allow ;
183- }
184- } ,
185- }
186-
187- if allow_read_only {
188- PermissionEvalResult :: Allow
189- } else {
190- PermissionEvalResult :: Ask
191- }
192- } )
193- . collect :: < Vec < _ > > ( ) ;
163+ let mut deny_list = Vec :: < PermissionEvalResult > :: new ( ) ;
164+ let mut ask = false ;
165+
166+ for op in & self . operations {
167+ match op {
168+ FsReadOperation :: Line ( FsLine { path, .. } )
169+ | FsReadOperation :: Directory ( FsDirectory { path, .. } )
170+ | FsReadOperation :: Search ( FsSearch { path, .. } ) => {
171+ let denied_match_set = deny_set. matches ( path) ;
172+ if !denied_match_set. is_empty ( ) {
173+ let deny_res = PermissionEvalResult :: Deny ( {
174+ denied_match_set
175+ . iter ( )
176+ . filter_map ( |i| sanitized_deny_list. get ( * i) . map ( |s| ( * s) . clone ( ) ) )
177+ . collect :: < Vec < _ > > ( )
178+ } ) ;
179+ deny_list. push ( deny_res) ;
180+ continue ;
181+ }
182+
183+ // We only want to ask if we are not allowing read only
184+ // operation
185+ if !allow_read_only && !allow_set. is_match ( path) {
186+ ask = true ;
187+ }
188+ } ,
189+ FsReadOperation :: Image ( fs_image) => {
190+ let paths = & fs_image. image_paths ;
191+ let denied_match_set =
192+ paths. iter ( ) . flat_map ( |p| deny_set. matches ( p) ) . collect :: < Vec < _ > > ( ) ;
193+ if !denied_match_set. is_empty ( ) {
194+ let deny_res = PermissionEvalResult :: Deny ( {
195+ denied_match_set
196+ . iter ( )
197+ . filter_map ( |i| sanitized_deny_list. get ( * i) . map ( |s| ( * s) . clone ( ) ) )
198+ . collect :: < Vec < _ > > ( )
199+ } ) ;
200+ deny_list. push ( deny_res) ;
201+ continue ;
202+ }
203+
204+ // We only want to ask if we are not allowing read only
205+ // operation
206+ if !allow_read_only && !paths. iter ( ) . any ( |path| allow_set. is_match ( path) ) {
207+ ask = true ;
208+ }
209+ } ,
210+ }
211+ }
194212
195- if eval_res. contains ( & PermissionEvalResult :: Deny ) {
196- PermissionEvalResult :: Deny
197- } else if eval_res. contains ( & PermissionEvalResult :: Ask ) {
213+ if !deny_list. is_empty ( ) {
214+ PermissionEvalResult :: Deny ( {
215+ deny_list. into_iter ( ) . fold ( Vec :: < String > :: new ( ) , |mut acc, res| {
216+ if let PermissionEvalResult :: Deny ( mut rules) = res {
217+ acc. append ( & mut rules) ;
218+ }
219+ acc
220+ } )
221+ } )
222+ } else if ask {
198223 PermissionEvalResult :: Ask
199224 } else {
200225 PermissionEvalResult :: Allow
@@ -813,7 +838,13 @@ fn format_mode(mode: u32) -> [char; 9] {
813838
814839#[ cfg( test) ]
815840mod tests {
841+ use std:: collections:: {
842+ HashMap ,
843+ HashSet ,
844+ } ;
845+
816846 use super :: * ;
847+ use crate :: cli:: agent:: ToolSettingTarget ;
817848 use crate :: cli:: chat:: util:: test:: {
818849 TEST_FILE_CONTENTS ,
819850 TEST_FILE_PATH ,
@@ -1323,6 +1354,7 @@ mod tests {
13231354 panic ! ( "expected text output for batch operations" ) ;
13241355 }
13251356 }
1357+
13261358 #[ tokio:: test]
13271359 async fn test_fs_read_empty_operations ( ) {
13281360 let os = Os :: new ( ) . await . unwrap ( ) ;
@@ -1343,4 +1375,49 @@ mod tests {
13431375 . contains( "At least one operation must be provided" )
13441376 ) ;
13451377 }
1378+
1379+ #[ test]
1380+ fn test_eval_perm ( ) {
1381+ const DENIED_PATH_ONE : & str = "/some/denied/path" ;
1382+ const DENIED_PATH_GLOB : & str = "/denied/glob/**/path" ;
1383+
1384+ let agent = Agent {
1385+ name : "test_agent" . to_string ( ) ,
1386+ allowed_tools : {
1387+ let mut allowed_tools = HashSet :: < String > :: new ( ) ;
1388+ allowed_tools. insert ( "fs_read" . to_string ( ) ) ;
1389+ allowed_tools
1390+ } ,
1391+ tools_settings : {
1392+ let mut map = HashMap :: < ToolSettingTarget , serde_json:: Value > :: new ( ) ;
1393+ map. insert (
1394+ ToolSettingTarget ( "fs_read" . to_string ( ) ) ,
1395+ serde_json:: json!( {
1396+ "deniedPaths" : [ DENIED_PATH_ONE , DENIED_PATH_GLOB ]
1397+ } ) ,
1398+ ) ;
1399+ map
1400+ } ,
1401+ ..Default :: default ( )
1402+ } ;
1403+
1404+ let tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1405+ "operations" : [
1406+ { "path" : DENIED_PATH_ONE , "mode" : "Line" , "start_line" : 1 , "end_line" : 2 } ,
1407+ { "path" : "/denied/glob" , "mode" : "Directory" } ,
1408+ { "path" : "/denied/glob/child_one/path" , "mode" : "Directory" } ,
1409+ { "path" : "/denied/glob/child_one/grand_child_one/path" , "mode" : "Directory" } ,
1410+ { "path" : TEST_FILE_PATH , "mode" : "Search" , "pattern" : "hello" }
1411+ ] ,
1412+ } ) )
1413+ . unwrap ( ) ;
1414+
1415+ let res = tool. eval_perm ( & agent) ;
1416+ assert ! ( matches!(
1417+ res,
1418+ PermissionEvalResult :: Deny ( ref deny_list)
1419+ if deny_list. iter( ) . filter( |p| * p == DENIED_PATH_GLOB ) . collect:: <Vec <_>>( ) . len( ) == 2
1420+ && deny_list. iter( ) . filter( |p| * p == DENIED_PATH_ONE ) . collect:: <Vec <_>>( ) . len( ) == 1
1421+ ) ) ;
1422+ }
13461423}
0 commit comments