@@ -13,6 +13,137 @@ const ctx = {
1313 metadata : ( ) => { } ,
1414}
1515
16+ describe ( "tool.read external_directory permission" , ( ) => {
17+ test ( "allows reading absolute path inside project directory" , async ( ) => {
18+ await using tmp = await tmpdir ( {
19+ init : async ( dir ) => {
20+ await Bun . write ( path . join ( dir , "test.txt" ) , "hello world" )
21+ await Bun . write (
22+ path . join ( dir , "opencode.json" ) ,
23+ JSON . stringify ( {
24+ permission : {
25+ external_directory : "deny" ,
26+ } ,
27+ } ) ,
28+ )
29+ } ,
30+ } )
31+ await Instance . provide ( {
32+ directory : tmp . path ,
33+ fn : async ( ) => {
34+ const read = await ReadTool . init ( )
35+ const result = await read . execute ( { filePath : path . join ( tmp . path , "test.txt" ) } , ctx )
36+ expect ( result . output ) . toContain ( "hello world" )
37+ } ,
38+ } )
39+ } )
40+
41+ test ( "allows reading file in subdirectory inside project directory" , async ( ) => {
42+ await using tmp = await tmpdir ( {
43+ init : async ( dir ) => {
44+ await Bun . write ( path . join ( dir , "subdir" , "test.txt" ) , "nested content" )
45+ await Bun . write (
46+ path . join ( dir , "opencode.json" ) ,
47+ JSON . stringify ( {
48+ permission : {
49+ external_directory : "deny" ,
50+ } ,
51+ } ) ,
52+ )
53+ } ,
54+ } )
55+ await Instance . provide ( {
56+ directory : tmp . path ,
57+ fn : async ( ) => {
58+ const read = await ReadTool . init ( )
59+ const result = await read . execute ( { filePath : path . join ( tmp . path , "subdir" , "test.txt" ) } , ctx )
60+ expect ( result . output ) . toContain ( "nested content" )
61+ } ,
62+ } )
63+ } )
64+
65+ test ( "denies reading absolute path outside project directory" , async ( ) => {
66+ await using outerTmp = await tmpdir ( {
67+ init : async ( dir ) => {
68+ await Bun . write ( path . join ( dir , "secret.txt" ) , "secret data" )
69+ } ,
70+ } )
71+ await using tmp = await tmpdir ( {
72+ init : async ( dir ) => {
73+ await Bun . write (
74+ path . join ( dir , "opencode.json" ) ,
75+ JSON . stringify ( {
76+ permission : {
77+ external_directory : "deny" ,
78+ } ,
79+ } ) ,
80+ )
81+ } ,
82+ } )
83+ await Instance . provide ( {
84+ directory : tmp . path ,
85+ fn : async ( ) => {
86+ const read = await ReadTool . init ( )
87+ await expect ( read . execute ( { filePath : path . join ( outerTmp . path , "secret.txt" ) } , ctx ) ) . rejects . toThrow (
88+ "not in the current working directory" ,
89+ )
90+ } ,
91+ } )
92+ } )
93+
94+ test ( "denies reading relative path that traverses outside project directory" , async ( ) => {
95+ await using tmp = await tmpdir ( {
96+ init : async ( dir ) => {
97+ await Bun . write (
98+ path . join ( dir , "opencode.json" ) ,
99+ JSON . stringify ( {
100+ permission : {
101+ external_directory : "deny" ,
102+ } ,
103+ } ) ,
104+ )
105+ } ,
106+ } )
107+ await Instance . provide ( {
108+ directory : tmp . path ,
109+ fn : async ( ) => {
110+ const read = await ReadTool . init ( )
111+ await expect ( read . execute ( { filePath : "../../../etc/passwd" } , ctx ) ) . rejects . toThrow (
112+ "not in the current working directory" ,
113+ )
114+ } ,
115+ } )
116+ } )
117+
118+ test ( "allows reading outside project directory when external_directory is allow" , async ( ) => {
119+ await using outerTmp = await tmpdir ( {
120+ init : async ( dir ) => {
121+ await Bun . write ( path . join ( dir , "external.txt" ) , "external content" )
122+ } ,
123+ } )
124+ await using tmp = await tmpdir ( {
125+ init : async ( dir ) => {
126+ await Bun . write (
127+ path . join ( dir , "opencode.json" ) ,
128+ JSON . stringify ( {
129+ permission : {
130+ external_directory : "allow" ,
131+ } ,
132+ } ) ,
133+ )
134+ } ,
135+ } )
136+ await Instance . provide ( {
137+ directory : tmp . path ,
138+ fn : async ( ) => {
139+ const read = await ReadTool . init ( )
140+ const result = await read . execute ( { filePath : path . join ( outerTmp . path , "external.txt" ) } , ctx )
141+ expect ( result . output ) . toContain ( "external content" )
142+ } ,
143+ } )
144+ } )
145+ } )
146+
16147describe ( "tool.read env file blocking" , ( ) => {
17148 test . each ( [
18149 [ ".env" , true ] ,
0 commit comments