@@ -59,4 +59,186 @@ describe("tool.bash", () => {
5959 } ,
6060 } )
6161 } )
62+
63+ test ( "should allow commands when bash permission='allow' via wildcard" , async ( ) => {
64+ const { Agent } = await import ( "../../src/agent/agent" )
65+ const originalGet = Agent . get
66+
67+ // Mock Agent.get to return agent with bash='allow' via wildcard
68+ Agent . get = mock ( async ( ) => ( {
69+ permission : {
70+ edit : "allow" as const ,
71+ bash : { "*" : "allow" as const } ,
72+ webfetch : "allow" as const ,
73+ external_directory : "ask" as const ,
74+ doom_loop : "ask" as const ,
75+ } ,
76+ } ) ) as any
77+
78+ const originalAsk = Permission . ask
79+ const permissionAskMock = mock ( async ( ) => { } )
80+ Permission . ask = permissionAskMock
81+
82+ try {
83+ await Instance . provide ( {
84+ directory : projectRoot ,
85+ fn : async ( ) => {
86+ const bash = await BashTool . init ( )
87+ const result = await bash . execute (
88+ {
89+ command : "echo 'permission test'" ,
90+ description : "Test bash permission" ,
91+ } ,
92+ ctx ,
93+ )
94+
95+ expect ( result . metadata . exit ) . toBe ( 0 )
96+ expect ( result . metadata . output ) . toContain ( "permission test" )
97+
98+ // Verify Permission.ask was NOT called
99+ expect ( permissionAskMock ) . not . toHaveBeenCalled ( )
100+ } ,
101+ } )
102+ } finally {
103+ Agent . get = originalGet
104+ Permission . ask = originalAsk
105+ }
106+ } )
107+
108+ test ( "should deny commands when bash permission='deny' via wildcard" , async ( ) => {
109+ const { Agent } = await import ( "../../src/agent/agent" )
110+ const originalGet = Agent . get
111+
112+ // Mock Agent.get to return agent with bash='deny' via wildcard
113+ Agent . get = mock ( async ( ) => ( {
114+ permission : {
115+ edit : "allow" as const ,
116+ bash : { "*" : "deny" as const } ,
117+ webfetch : "allow" as const ,
118+ external_directory : "ask" as const ,
119+ doom_loop : "ask" as const ,
120+ } ,
121+ } ) ) as any
122+
123+ const originalAsk = Permission . ask
124+ const permissionAskMock = mock ( async ( ) => { } )
125+ Permission . ask = permissionAskMock
126+
127+ try {
128+ await Instance . provide ( {
129+ directory : projectRoot ,
130+ fn : async ( ) => {
131+ const bash = await BashTool . init ( )
132+ await expect (
133+ bash . execute (
134+ {
135+ command : "echo 'should be denied'" ,
136+ description : "Test bash permission deny" ,
137+ } ,
138+ ctx ,
139+ ) ,
140+ ) . rejects . toThrow ( "Permission denied: Command not allowed by bash permissions" )
141+
142+ // Verify Permission.ask was NOT called
143+ expect ( permissionAskMock ) . not . toHaveBeenCalled ( )
144+ } ,
145+ } )
146+ } finally {
147+ Agent . get = originalGet
148+ Permission . ask = originalAsk
149+ }
150+ } )
151+
152+ test ( "should ask for permission when bash permission='ask' via wildcard" , async ( ) => {
153+ const { Agent } = await import ( "../../src/agent/agent" )
154+ const originalGet = Agent . get
155+
156+ // Mock Agent.get to return agent with bash='ask' via wildcard
157+ Agent . get = mock ( async ( ) => ( {
158+ permission : {
159+ edit : "allow" as const ,
160+ bash : { "*" : "ask" as const } ,
161+ webfetch : "allow" as const ,
162+ external_directory : "ask" as const ,
163+ doom_loop : "ask" as const ,
164+ } ,
165+ } ) ) as any
166+
167+ const originalAsk = Permission . ask
168+ const permissionAskMock = mock ( async ( input : any ) => {
169+ expect ( input . type ) . toBe ( "bash" )
170+ // Resolve without throwing to simulate user approval
171+ } )
172+ Permission . ask = permissionAskMock
173+
174+ try {
175+ await Instance . provide ( {
176+ directory : projectRoot ,
177+ fn : async ( ) => {
178+ const bash = await BashTool . init ( )
179+ const result = await bash . execute (
180+ {
181+ command : "echo 'ask permission'" ,
182+ description : "Test bash permission ask" ,
183+ } ,
184+ ctx ,
185+ )
186+
187+ expect ( result . metadata . exit ) . toBe ( 0 )
188+ expect ( result . metadata . output ) . toContain ( "ask permission" )
189+
190+ // Verify Permission.ask WAS called
191+ expect ( permissionAskMock ) . toHaveBeenCalled ( )
192+ } ,
193+ } )
194+ } finally {
195+ Agent . get = originalGet
196+ Permission . ask = originalAsk
197+ }
198+ } )
199+
200+ test ( "should deny when no wildcard match (undefined permission)" , async ( ) => {
201+ const { Agent } = await import ( "../../src/agent/agent" )
202+ const originalGet = Agent . get
203+
204+ // Mock Agent.get to return agent with specific command allowed, but not echo
205+ Agent . get = mock ( async ( ) => ( {
206+ permission : {
207+ edit : "allow" as const ,
208+ bash : { "ls *" : "allow" as const } , // Only ls is allowed
209+ webfetch : "allow" as const ,
210+ external_directory : "ask" as const ,
211+ doom_loop : "ask" as const ,
212+ } ,
213+ } ) ) as any
214+
215+ const originalAsk = Permission . ask
216+ const permissionAskMock = mock ( async ( ) => { } )
217+ Permission . ask = permissionAskMock
218+
219+ try {
220+ await Instance . provide ( {
221+ directory : projectRoot ,
222+ fn : async ( ) => {
223+ const bash = await BashTool . init ( )
224+ // echo doesn't match any pattern, should default to deny
225+ await expect (
226+ bash . execute (
227+ {
228+ command : "echo 'no match'" ,
229+ description : "Test undefined permission" ,
230+ } ,
231+ ctx ,
232+ ) ,
233+ ) . rejects . toThrow ( "Permission denied: Command not allowed by bash permissions" )
234+
235+ // Verify Permission.ask was NOT called
236+ expect ( permissionAskMock ) . not . toHaveBeenCalled ( )
237+ } ,
238+ } )
239+ } finally {
240+ Agent . get = originalGet
241+ Permission . ask = originalAsk
242+ }
243+ } )
62244} )
0 commit comments