spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / sec-default / tests / register.test.ts
458 lines · 12.4 KBRaw
1import { describe, expect, test, tier } from 'claude-code/testing'
2
3import Hooks from '../hooks'
4import Fixtures from './fixtures'
5
6tier('prepend')
7
8describe('register', () => {
9 test(
10 'under an MCP allowlist a plugin the person installed may not add a tool',
11 {
12 plugins: [
13 Fixtures.registering('suite', 'prepend'),
14 Fixtures.registering('mine'),
15 Fixtures.registering('bundled', 'builtin'),
16 ],
17 },
18 async ($, on) => {
19 on('settings.read', () => ({ value: Fixtures.ALLOWLIST }))
20
21 const registered = Fixtures.toolsRegistered(on)
22
23 await $.session.start(Fixtures.SESSION)
24
25 expect(registered).toEqual(['suite', 'bundled'])
26 },
27 )
28
29 test(
30 'with no allowlist, a plugin the person installed adds its tool',
31 { plugins: [Fixtures.registering('mine')] },
32 async ($, on) => {
33 on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
34
35 const registered = Fixtures.toolsRegistered(on)
36
37 await $.session.start(Fixtures.SESSION)
38
39 expect(registered).toEqual(['mine'])
40 },
41 )
42
43 test(
44 "an organization's registration passes over a refusing user plugin",
45 {
46 plugins: [
47 Fixtures.registering('suite', 'prepend'),
48 Fixtures.denying,
49 Fixtures.registering('bundled', 'builtin'),
50 ],
51 },
52 async ($, on) => {
53 on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY }))
54
55 const registered = Fixtures.toolsRegistered(on)
56
57 await $.session.start(Fixtures.SESSION)
58
59 expect(
60 registered,
61 "a built-in's registration still meets the user's deny",
62 ).toEqual(['suite'])
63 },
64 )
65
66 test(
67 'a policy that cannot be read counts as one in force',
68 { plugins: [Fixtures.registering('mine')] },
69 async ($, on) => {
70 const stopPolicy = Fixtures.policyUntilStopped(
71 on,
72 Fixtures.NO_ALLOWLIST,
73 'managed settings unreadable',
74 )
75
76 const registered = Fixtures.toolsRegistered(on)
77
78 on('prompt.section', ($, e) => ({ text: e.text }))
79
80 await $.prompt.section(Fixtures.MEMORY)
81 stopPolicy()
82 await $.session.start(Fixtures.SESSION)
83
84 expect(registered).toEqual([])
85 },
86 )
87
88 test(
89 'the organization tools are listed as its tiers listed them',
90 { plugins: [Fixtures.relabeling, Fixtures.listing] },
91 async ($, on) => {
92 on('settings.read', () => ({ value: Fixtures.ALLOWLIST }))
93 on('tool.list', () => ({ value: [...Fixtures.TOOLS] }))
94
95 const { text } = await $.command.run(Fixtures.TOOLS_COMMAND)
96
97 expect(text?.split('\n')).toEqual([
98 'mcp__corp__search: Searches the corp wiki.',
99 'Bash: relabeled',
100 ])
101 },
102 )
103
104 test(
105 'a server policy delivers itself counts as a tool policy, unlisted',
106 { plugins: [Fixtures.relabeling, Fixtures.listing] },
107 async ($, on) => {
108 on('settings.read', () => ({ value: Fixtures.SERVER_POLICY }))
109 on('tool.list', () => ({ value: [...Fixtures.TOOLS] }))
110
111 const { text } = await $.command.run(Fixtures.TOOLS_COMMAND)
112
113 expect(text?.split('\n')).toEqual([
114 'mcp__corp__search: Searches the corp wiki.',
115 'Bash: relabeled',
116 ])
117 },
118 )
119
120 test(
121 "with no policy to read the organization's listing stands whole",
122 { plugins: [Fixtures.relabeling, Fixtures.listing] },
123 async ($, on) => {
124 const stopPolicy = Fixtures.policyUntilStopped(
125 on,
126 Fixtures.ALLOWLIST,
127 'settings unreadable',
128 )
129
130 on('prompt.section', ($, e) => ({ text: e.text }))
131 on('tool.list', () => ({ value: [...Fixtures.TOOLS] }))
132
133 await $.prompt.section(Fixtures.MEMORY)
134 stopPolicy()
135
136 const { text } = await $.command.run(Fixtures.TOOLS_COMMAND)
137
138 expect(text?.split('\n')).toEqual([
139 'mcp__corp__search: Searches the corp wiki.',
140 'Bash: Runs a command.',
141 ])
142 },
143 )
144
145 test(
146 'a prompt section passes over the plugins the person installed',
147 { plugins: [Fixtures.dropping, Fixtures.signing] },
148 async ($, on) => {
149 on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
150 on('prompt.section', ($, e) => ({ text: e.text }))
151
152 expect(await $.prompt.section(Fixtures.MEMORY)).toEqual({
153 text: 'the org says hi (signed)',
154 })
155 },
156 )
157
158 test(
159 "a user plugin's rewrite of policy is skipped for every other reader",
160 {
161 plugins: [
162 Fixtures.stripping,
163 Fixtures.reading,
164 Fixtures.registering('mine'),
165 ],
166 },
167 async ($, on) => {
168 on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY }))
169
170 const registered = Fixtures.toolsRegistered(on)
171 const { text } = await $.command.run(Fixtures.POLICY_COMMAND)
172
173 await $.session.start(Fixtures.SESSION)
174
175 expect(
176 JSON.parse(text ?? 'null'),
177 "another user plugin's read sees the allowlist the stripper hides",
178 ).toEqual(Fixtures.MANAGED_POLICY)
179
180 expect(
181 registered,
182 "sec-default's own tool.register hook still reads the allowlist",
183 ).toEqual([])
184 },
185 )
186
187 test(
188 "an organization provider's subject passes over the user plugins",
189 { plugins: [Fixtures.marking] },
190 async ($, on) => {
191 on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
192 Fixtures.subjectsEchoed(on)
193
194 for (const provider of Fixtures.ORG_PROVIDERS) {
195 expect(
196 await $.tool.describe(
197 Fixtures.toolDescribed('mcp__corp__search', provider),
198 ),
199 ).toEqual({ description: 'd' })
200
201 expect(
202 (
203 await $.command.describe(
204 Fixtures.commandDescribed('suite:deploy', provider),
205 )
206 ).isHidden,
207 ).toBe(false)
208
209 expect(
210 await $.agent.offer(
211 Fixtures.agentOffered('suite:reviewer', provider),
212 ),
213 ).toEqual({ isOffered: true })
214
215 expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({
216 model: 'core',
217 })
218 }
219
220 for (const provider of Fixtures.USER_REACHABLE_PROVIDERS) {
221 expect(
222 await $.tool.describe(
223 Fixtures.toolDescribed('mcp__mine__search', provider),
224 ),
225 ).toEqual({ description: 'user: d' })
226
227 expect(
228 (
229 await $.command.describe(
230 Fixtures.commandDescribed('mine:deploy', provider),
231 )
232 ).isHidden,
233 ).toBe(true)
234
235 expect(
236 await $.agent.offer(Fixtures.agentOffered('reviewer', provider)),
237 ).toEqual({ isOffered: false })
238
239 expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({
240 model: 'user',
241 })
242 }
243 },
244 )
245
246 test(
247 'an odd provider passes over the user plugins: it fails closed',
248 { plugins: [Fixtures.marking] },
249 async ($, on) => {
250 on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
251 Fixtures.subjectsEchoed(on)
252
253 for (const provider of Fixtures.ODD_PROVIDERS) {
254 expect(
255 await $.tool.describe(Fixtures.toolDescribed('Bash', provider)),
256 ).toEqual({ description: 'd' })
257
258 expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({
259 model: 'core',
260 })
261 }
262 },
263 )
264
265 test(
266 'a subject decision reads no policy; a burst of listings reads once',
267 { plugins: [Fixtures.listing] },
268 async ($, on) => {
269 const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY)
270
271 Fixtures.subjectsEchoed(on)
272 on('tool.list', () => ({ value: [...Fixtures.TOOLS] }))
273
274 for (const provider of Fixtures.ORG_PROVIDERS) {
275 await $.tool.describe(Fixtures.toolDescribed('mcp__corp__x', provider))
276 await $.agent.spawn(Fixtures.agentSpawned(provider))
277 }
278
279 expect(reads(), "one read: the person's plugin was admitted").toBe(1)
280
281 await Promise.all([
282 $.command.run(Fixtures.TOOLS_COMMAND),
283 $.command.run(Fixtures.TOOLS_COMMAND),
284 $.command.run(Fixtures.TOOLS_COMMAND),
285 ])
286
287 expect(reads()).toBe(2)
288 },
289 )
290
291 test(
292 'the refusal a user-tier caller reads names the allowlist',
293 {
294 plugins: [
295 {
296 name: 'asking',
297 register(on) {
298 on('command.run', { command: 'greet' }, $ =>
299 $.tool
300 .register({
301 name: 'greet',
302 description: 'Says hello.',
303 inputSchema: { type: 'object' },
304 })
305 .then(
306 () => ({ text: 'registered' }),
307 (error: unknown) => ({ text: String(error) }),
308 ),
309 )
310 },
311 },
312 ],
313 },
314 async ($, on) => {
315 on('settings.read', () => ({ value: Fixtures.ALLOWLIST }))
316
317 const { text } = await $.command.run({
318 command: 'greet',
319 args: '',
320 origin: { kind: 'composer' },
321 presentation: Fixtures.FULLSCREEN,
322 })
323
324 expect(text).toEndWith(
325 `asking: $.tool.register: ${Hooks.TOOL_REGISTER_REFUSAL}`,
326 )
327 },
328 )
329
330 test(
331 "under allowManagedModsOnly a person's mod is kept out, told why",
332 { plugins: [Fixtures.registering('mine')] },
333 async ($, on) => {
334 on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY }))
335
336 const registered = Fixtures.toolsRegistered(on)
337
338 await expect($.session.start(Fixtures.SESSION)).rejects.toThrow(
339 Hooks.managedModsOnlyRefusal('mine'),
340 )
341
342 expect(registered, 'no hook of it ever ran').toEqual([])
343 },
344 )
345
346 test(
347 "under it the organization's mods and the built-ins load",
348 {
349 plugins: [
350 Fixtures.registering('suite', 'prepend'),
351 Fixtures.registering('after', 'append'),
352 Fixtures.registering('bundled', 'builtin'),
353 ],
354 },
355 async ($, on) => {
356 on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY }))
357
358 const registered = Fixtures.toolsRegistered(on)
359
360 await $.session.start(Fixtures.SESSION)
361
362 expect(registered).toEqual(['suite', 'after', 'bundled'])
363 },
364 )
365
366 test(
367 'set to false, a mod the person installed loads as before',
368 { plugins: [Fixtures.registering('mine')] },
369 async ($, on) => {
370 on('settings.read', () => ({ value: Fixtures.modsPolicyOf(false) }))
371
372 const registered = Fixtures.toolsRegistered(on)
373
374 await $.session.start(Fixtures.SESSION)
375
376 expect(registered).toEqual(['mine'])
377 },
378 )
379
380 test(
381 "a person's copy under an organization mod's name is still theirs",
382 { plugins: [Fixtures.registering('suite')] },
383 async ($, on) => {
384 on('settings.read', () => ({
385 value: { ...Fixtures.MANAGED_POLICY, ...Fixtures.MANAGED_MODS_ONLY },
386 }))
387
388 const registered = Fixtures.toolsRegistered(on)
389
390 await expect($.session.start(Fixtures.SESSION)).rejects.toThrow(
391 Hooks.managedModsOnlyRefusal('suite'),
392 )
393
394 expect(registered).toEqual([])
395 },
396 )
397
398 test(
399 "only managed settings are asked: a person's own cannot loosen it",
400 { plugins: [Fixtures.registering('mine')] },
401 async ($, on) => {
402 Fixtures.policyBySource(
403 on,
404 Fixtures.MANAGED_MODS_ONLY,
405 Fixtures.modsPolicyOf(false),
406 )
407
408 const registered = Fixtures.toolsRegistered(on)
409
410 await expect($.session.start(Fixtures.SESSION)).rejects.toThrow(
411 Hooks.managedModsOnlyRefusal('mine'),
412 )
413
414 expect(registered).toEqual([])
415 },
416 )
417
418 test(
419 "nor turn it on: set only in a person's settings, it is not read",
420 { plugins: [Fixtures.registering('mine')] },
421 async ($, on) => {
422 Fixtures.policyBySource(
423 on,
424 Fixtures.NO_ALLOWLIST,
425 Fixtures.MANAGED_MODS_ONLY,
426 )
427
428 const registered = Fixtures.toolsRegistered(on)
429
430 await $.session.start(Fixtures.SESSION)
431
432 expect(registered).toEqual(['mine'])
433 },
434 )
435
436 test(
437 "a policy that cannot be read keeps a person's mod out: the hook's catch",
438 { plugins: [Fixtures.registering('mine')] },
439 async ($, on) => {
440 on('settings.read', () => ({ deny: 'managed settings unreadable' }))
441
442 const lines = Fixtures.logged(on)
443 const registered = Fixtures.toolsRegistered(on)
444
445 await expect($.session.start(Fixtures.SESSION)).rejects.toThrow(
446 Hooks.managedModsOnlyRefusal('mine'),
447 )
448
449 expect({ registered, lines }).toEqual({
450 registered: [],
451 lines: [
452 'debug: plugin.register hook failed judging mine (throw): ' +
453 'sec-default: $.settings.read: managed settings unreadable',
454 ],
455 })
456 },
457 )
458})