Skip to content

Commit 2f6e86b

Browse files
authored
feat(amazonq): support for wildcard permissions from agent config (aws#2249)
1 parent 4d3b938 commit 2f6e86b

File tree

3 files changed

+433
-141
lines changed

3 files changed

+433
-141
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
* All Rights Reserved. SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { expect } from 'chai'
7+
import { AgentPermissionManager } from './agentPermissionManager'
8+
import { AgentConfig, McpPermissionType } from './mcpTypes'
9+
10+
describe('AgentPermissionManager', () => {
11+
let agentConfig: AgentConfig
12+
let manager: AgentPermissionManager
13+
14+
beforeEach(() => {
15+
agentConfig = {
16+
name: 'test-agent',
17+
description: 'Test agent',
18+
mcpServers: {},
19+
tools: [],
20+
allowedTools: [],
21+
}
22+
manager = new AgentPermissionManager(agentConfig)
23+
})
24+
25+
describe('matchesPattern', () => {
26+
it('matches exact patterns', () => {
27+
expect(manager['matchesPattern']('tool1', 'tool1')).to.be.true
28+
expect(manager['matchesPattern']('tool1', 'tool2')).to.be.false
29+
})
30+
31+
it('matches wildcard patterns', () => {
32+
expect(manager['matchesPattern']('tool1', '*')).to.be.true
33+
expect(manager['matchesPattern']('tool1', 'tool*')).to.be.true
34+
expect(manager['matchesPattern']('tool1', '*1')).to.be.true
35+
expect(manager['matchesPattern']('tool1', 'to*l1')).to.be.true
36+
expect(manager['matchesPattern']('tool1', 'foo*')).to.be.false
37+
})
38+
39+
it('matches question mark patterns', () => {
40+
expect(manager['matchesPattern']('tool1', 'tool?')).to.be.true
41+
expect(manager['matchesPattern']('tool1', '?ool1')).to.be.true
42+
expect(manager['matchesPattern']('tool1', 'tool??')).to.be.false
43+
})
44+
45+
it('escapes regex special characters', () => {
46+
expect(manager['matchesPattern']('tool.1', 'tool.1')).to.be.true
47+
expect(manager['matchesPattern']('tool+1', 'tool+1')).to.be.true
48+
expect(manager['matchesPattern']('tool[1]', 'tool[1]')).to.be.true
49+
})
50+
})
51+
52+
describe('isToolEnabled', () => {
53+
it('returns true for exact tool matches', () => {
54+
agentConfig.tools = ['tool1', '@server/tool2']
55+
expect(manager.isToolEnabled('', 'tool1')).to.be.true
56+
expect(manager.isToolEnabled('server', 'tool2')).to.be.true
57+
})
58+
59+
it('returns true for server prefix matches', () => {
60+
agentConfig.tools = ['@server']
61+
expect(manager.isToolEnabled('server', 'tool1')).to.be.true
62+
})
63+
64+
it('returns true for wildcard matches', () => {
65+
agentConfig.tools = ['*']
66+
expect(manager.isToolEnabled('server', 'tool1')).to.be.true
67+
68+
agentConfig.tools = ['tool*']
69+
expect(manager.isToolEnabled('', 'tool1')).to.be.true
70+
expect(manager.isToolEnabled('', 'foo1')).to.be.false
71+
})
72+
73+
it('returns false for non-matching tools', () => {
74+
agentConfig.tools = ['tool1']
75+
expect(manager.isToolEnabled('', 'tool2')).to.be.false
76+
expect(manager.isToolEnabled('server', 'tool1')).to.be.false
77+
})
78+
})
79+
80+
describe('isToolAlwaysAllowed', () => {
81+
it('returns true for exact tool matches', () => {
82+
agentConfig.allowedTools = ['tool1', '@server/tool2']
83+
expect(manager.isToolAlwaysAllowed('', 'tool1')).to.be.true
84+
expect(manager.isToolAlwaysAllowed('server', 'tool2')).to.be.true
85+
})
86+
87+
it('returns true for server prefix matches', () => {
88+
agentConfig.allowedTools = ['@server']
89+
expect(manager.isToolAlwaysAllowed('server', 'tool1')).to.be.true
90+
})
91+
92+
it('returns true for wildcard matches', () => {
93+
agentConfig.allowedTools = ['tool*']
94+
expect(manager.isToolAlwaysAllowed('', 'tool1')).to.be.true
95+
expect(manager.isToolAlwaysAllowed('', 'foo1')).to.be.false
96+
})
97+
98+
it('returns false for non-matching tools', () => {
99+
agentConfig.allowedTools = ['tool1']
100+
expect(manager.isToolAlwaysAllowed('', 'tool2')).to.be.false
101+
})
102+
})
103+
104+
describe('getToolPermission', () => {
105+
it('returns alwaysAllow for always allowed tools', () => {
106+
agentConfig.allowedTools = ['tool1']
107+
expect(manager.getToolPermission('', 'tool1')).to.equal(McpPermissionType.alwaysAllow)
108+
})
109+
110+
it('returns ask for enabled but not always allowed tools', () => {
111+
agentConfig.tools = ['tool1']
112+
expect(manager.getToolPermission('', 'tool1')).to.equal(McpPermissionType.ask)
113+
})
114+
115+
it('returns deny for non-enabled tools', () => {
116+
expect(manager.getToolPermission('', 'tool1')).to.equal(McpPermissionType.deny)
117+
})
118+
119+
it('prioritizes alwaysAllow over enabled', () => {
120+
agentConfig.tools = ['tool1']
121+
agentConfig.allowedTools = ['tool1']
122+
expect(manager.getToolPermission('', 'tool1')).to.equal(McpPermissionType.alwaysAllow)
123+
})
124+
})
125+
126+
describe('setToolPermission', () => {
127+
it('sets deny permission correctly', () => {
128+
agentConfig.tools = ['tool1']
129+
agentConfig.allowedTools = ['tool1']
130+
131+
manager.setToolPermission('', 'tool1', McpPermissionType.deny)
132+
133+
expect(agentConfig.tools).to.not.include('tool1')
134+
expect(agentConfig.allowedTools).to.not.include('tool1')
135+
})
136+
137+
it('sets ask permission correctly', () => {
138+
manager.setToolPermission('', 'tool1', McpPermissionType.ask)
139+
140+
expect(agentConfig.tools).to.include('tool1')
141+
expect(agentConfig.allowedTools).to.not.include('tool1')
142+
})
143+
144+
it('sets alwaysAllow permission correctly', () => {
145+
manager.setToolPermission('', 'tool1', McpPermissionType.alwaysAllow)
146+
147+
expect(agentConfig.tools).to.include('tool1')
148+
expect(agentConfig.allowedTools).to.include('tool1')
149+
})
150+
151+
it('removes conflicting wildcards', () => {
152+
agentConfig.tools = ['tool*']
153+
agentConfig.allowedTools = ['tool*']
154+
155+
manager.setToolPermission('', 'tool1', McpPermissionType.deny)
156+
157+
expect(agentConfig.tools).to.not.include('tool*')
158+
expect(agentConfig.allowedTools).to.not.include('tool*')
159+
})
160+
161+
it('handles server-scoped tools', () => {
162+
manager.setToolPermission('server', 'tool1', McpPermissionType.ask)
163+
164+
expect(agentConfig.tools).to.include('@server/tool1')
165+
})
166+
})
167+
168+
describe('setServerPermission', () => {
169+
it('sets deny permission for entire server', () => {
170+
agentConfig.tools = ['@server', '@server/tool1']
171+
agentConfig.allowedTools = ['@server/tool2']
172+
173+
manager.setServerPermission('server', McpPermissionType.deny)
174+
175+
expect(agentConfig.tools).to.not.include('@server')
176+
expect(agentConfig.tools).to.not.include('@server/tool1')
177+
expect(agentConfig.allowedTools).to.not.include('@server/tool2')
178+
})
179+
180+
it('sets ask permission for entire server', () => {
181+
manager.setServerPermission('server', McpPermissionType.ask)
182+
183+
expect(agentConfig.tools).to.include('@server')
184+
expect(agentConfig.allowedTools).to.not.include('@server')
185+
})
186+
187+
it('sets alwaysAllow permission for entire server', () => {
188+
manager.setServerPermission('server', McpPermissionType.alwaysAllow)
189+
190+
expect(agentConfig.tools).to.include('@server')
191+
expect(agentConfig.allowedTools).to.include('@server')
192+
})
193+
194+
it('removes specific tools when setting server permission', () => {
195+
agentConfig.tools = ['@server/tool1', '@server/tool2']
196+
agentConfig.allowedTools = ['@server/tool3']
197+
198+
manager.setServerPermission('server', McpPermissionType.ask)
199+
200+
expect(agentConfig.tools).to.not.include('@server/tool1')
201+
expect(agentConfig.tools).to.not.include('@server/tool2')
202+
expect(agentConfig.allowedTools).to.not.include('@server/tool3')
203+
expect(agentConfig.tools).to.include('@server')
204+
})
205+
})
206+
207+
describe('getAgentConfig', () => {
208+
it('returns the current agent config', () => {
209+
const config = manager.getAgentConfig()
210+
expect(config).to.equal(agentConfig)
211+
})
212+
})
213+
})

0 commit comments

Comments
 (0)