@@ -114,23 +114,31 @@ impl FsRead {
114
114
}
115
115
116
116
fn default_allow_read_only ( ) -> bool {
117
- true
117
+ false
118
118
}
119
119
120
120
let is_in_allowlist = matches_any_pattern ( & agent. allowed_tools , "fs_read" ) ;
121
- match agent. tools_settings . get ( "fs_read" ) {
122
- Some ( settings) => {
121
+ let settings = agent. tools_settings . get ( "fs_read" ) . cloned ( )
122
+ . unwrap_or_else ( || serde_json:: json!( { } ) ) ;
123
+
124
+ {
123
125
let Settings {
124
- allowed_paths,
126
+ mut allowed_paths,
125
127
denied_paths,
126
128
allow_read_only,
127
- } = match serde_json:: from_value :: < Settings > ( settings. clone ( ) ) {
129
+ } = match serde_json:: from_value :: < Settings > ( settings) {
128
130
Ok ( settings) => settings,
129
131
Err ( e) => {
130
132
error ! ( "Failed to deserialize tool settings for fs_read: {:?}" , e) ;
131
133
return PermissionEvalResult :: Ask ;
132
134
} ,
133
135
} ;
136
+
137
+ // Always add current working directory to allowed paths
138
+ if let Ok ( cwd) = os. env . current_dir ( ) {
139
+ allowed_paths. push ( cwd. to_string_lossy ( ) . to_string ( ) ) ;
140
+ }
141
+
134
142
let allow_set = {
135
143
let mut builder = GlobSetBuilder :: new ( ) ;
136
144
for path in & allowed_paths {
@@ -259,10 +267,7 @@ impl FsRead {
259
267
PermissionEvalResult :: Ask
260
268
} ,
261
269
}
262
- } ,
263
- None if is_in_allowlist => PermissionEvalResult :: Allow ,
264
- _ => PermissionEvalResult :: Ask ,
265
- }
270
+ }
266
271
}
267
272
268
273
pub async fn invoke ( & self , os : & Os , updates : & mut impl Write ) -> Result < InvokeOutput > {
@@ -862,6 +867,7 @@ fn format_mode(mode: u32) -> [char; 9] {
862
867
#[ cfg( test) ]
863
868
mod tests {
864
869
use std:: collections:: HashMap ;
870
+ use std:: path:: PathBuf ;
865
871
866
872
use super :: * ;
867
873
use crate :: cli:: agent:: ToolSettingTarget ;
@@ -1397,7 +1403,7 @@ mod tests {
1397
1403
}
1398
1404
1399
1405
#[ tokio:: test]
1400
- async fn test_eval_perm ( ) {
1406
+ async fn test_eval_perm_denied_path ( ) {
1401
1407
const DENIED_PATH_OR_FILE : & str = "/some/denied/path" ;
1402
1408
const DENIED_PATH_OR_FILE_GLOB : & str = "/denied/glob/**/path" ;
1403
1409
@@ -1447,4 +1453,88 @@ mod tests {
1447
1453
&& deny_list. iter( ) . filter( |p| * p == DENIED_PATH_OR_FILE ) . collect:: <Vec <_>>( ) . len( ) == 2
1448
1454
) ) ;
1449
1455
}
1456
+
1457
+ #[ tokio:: test]
1458
+ async fn test_eval_perm_allowed_path_and_cwd ( ) {
1459
+
1460
+ // by default the fake env uses "/" as the CWD.
1461
+ // change it to a sub folder so we can test fs_read reading files outside CWD
1462
+ let os = Os :: new ( ) . await . unwrap ( ) ;
1463
+ os. env . set_current_dir_for_test ( PathBuf :: from ( "/home/user" ) ) ;
1464
+
1465
+ let agent = Agent {
1466
+ name : "test_agent" . to_string ( ) ,
1467
+ tools_settings : {
1468
+ let mut map = HashMap :: new ( ) ;
1469
+ map. insert (
1470
+ ToolSettingTarget ( "fs_read" . to_string ( ) ) ,
1471
+ serde_json:: json!( {
1472
+ "allowedPaths" : [ "/explicitly/allowed/path" ]
1473
+ } ) ,
1474
+ ) ;
1475
+ map
1476
+ } ,
1477
+ ..Default :: default ( ) // Not in allowed_tools, allow_read_only = false
1478
+ } ;
1479
+
1480
+ // Test 1: Explicitly allowed path should work
1481
+ let allowed_tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1482
+ "operations" : [
1483
+ { "path" : "/explicitly/allowed/path" , "mode" : "Directory" } ,
1484
+ { "path" : "/explicitly/allowed/path/file.txt" , "mode" : "Line" } ,
1485
+ ]
1486
+ } ) ) . unwrap ( ) ;
1487
+ let res = allowed_tool. eval_perm ( & os, & agent) ;
1488
+ assert ! ( matches!( res, PermissionEvalResult :: Allow ) ) ;
1489
+
1490
+ // Test 2: CWD should always be allowed
1491
+ let cwd_tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1492
+ "operations" : [
1493
+ { "path" : "/home/user/" , "mode" : "Directory" } ,
1494
+ { "path" : "/home/user/file.txt" , "mode" : "Line" } ,
1495
+ ]
1496
+ } ) ) . unwrap ( ) ;
1497
+ let res = cwd_tool. eval_perm ( & os, & agent) ;
1498
+ assert ! ( matches!( res, PermissionEvalResult :: Allow ) ) ;
1499
+
1500
+ // Test 3: Outside CWD and not explicitly allowed should ask
1501
+ let outside_tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1502
+ "operations" : [
1503
+ { "path" : "/tmp/not/allowed/file.txt" , "mode" : "Line" }
1504
+ ]
1505
+ } ) ) . unwrap ( ) ;
1506
+ let res = outside_tool. eval_perm ( & os, & agent) ;
1507
+ assert ! ( matches!( res, PermissionEvalResult :: Ask ) ) ;
1508
+ }
1509
+
1510
+ #[ tokio:: test]
1511
+ async fn test_eval_perm_no_settings_cwd_behavior ( ) {
1512
+ let os = Os :: new ( ) . await . unwrap ( ) ;
1513
+ os. env . set_current_dir_for_test ( PathBuf :: from ( "/home/user" ) ) ;
1514
+
1515
+ let agent = Agent {
1516
+ name : "test_agent" . to_string ( ) ,
1517
+ tools_settings : HashMap :: new ( ) , // No fs_read settings
1518
+ ..Default :: default ( )
1519
+ } ;
1520
+
1521
+ // Test 1: CWD should be allowed even with no settings
1522
+ let cwd_tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1523
+ "operations" : [
1524
+ { "path" : "/home/user/" , "mode" : "Directory" } ,
1525
+ { "path" : "/home/user/file.txt" , "mode" : "Line" } ,
1526
+ ]
1527
+ } ) ) . unwrap ( ) ;
1528
+ let res = cwd_tool. eval_perm ( & os, & agent) ;
1529
+ assert ! ( matches!( res, PermissionEvalResult :: Allow ) ) ;
1530
+
1531
+ // Test 2: Outside CWD should ask for permission
1532
+ let outside_tool = serde_json:: from_value :: < FsRead > ( serde_json:: json!( {
1533
+ "operations" : [
1534
+ { "path" : "/tmp/not/allowed/file.txt" , "mode" : "Line" }
1535
+ ]
1536
+ } ) ) . unwrap ( ) ;
1537
+ let res = outside_tool. eval_perm ( & os, & agent) ;
1538
+ assert ! ( matches!( res, PermissionEvalResult :: Ask ) ) ;
1539
+ }
1450
1540
}
0 commit comments