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
| 1 | import { describe, expect, test, tier } from 'claude-code/testing' |
| 2 | |
| 3 | import Hooks from '../hooks' |
| 4 | import Fixtures from './fixtures' |
| 5 | |
| 6 | tier('prepend') |
| 7 | |
| 8 | describe('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 | "a plugin of theirs named as an organization's server adds no tool", |
| 90 | { |
| 91 | plugins: [ |
| 92 | Fixtures.registering('corp'), |
| 93 | Fixtures.registering('mine'), |
| 94 | Fixtures.registering('bundled', 'builtin'), |
| 95 | ], |
| 96 | }, |
| 97 | async ($, on) => { |
| 98 | on('settings.read', () => ({ value: Fixtures.SERVER_POLICY })) |
| 99 | |
| 100 | const registered = Fixtures.toolsRegistered(on) |
| 101 | |
| 102 | await $.session.start(Fixtures.SESSION) |
| 103 | |
| 104 | expect(registered).toEqual(['mine', 'bundled']) |
| 105 | }, |
| 106 | ) |
| 107 | |
| 108 | test( |
| 109 | "a plugin of theirs named as a seated plugin's own server adds no tool", |
| 110 | { |
| 111 | plugins: [ |
| 112 | Fixtures.registering('plugin-corp-suite-corp-jira'), |
| 113 | Fixtures.registering('mine'), |
| 114 | ], |
| 115 | }, |
| 116 | async ($, on) => { |
| 117 | on('settings.read', () => ({ value: Fixtures.SEATED_SUITE })) |
| 118 | |
| 119 | const registered = Fixtures.toolsRegistered(on) |
| 120 | |
| 121 | await $.session.start(Fixtures.SESSION) |
| 122 | |
| 123 | expect(registered).toEqual(['mine']) |
| 124 | }, |
| 125 | ) |
| 126 | |
| 127 | test( |
| 128 | "the organization's own plugin under such a name still adds its tool", |
| 129 | { plugins: [Fixtures.registering('corp', 'append')] }, |
| 130 | async ($, on) => { |
| 131 | on('settings.read', () => ({ value: Fixtures.SERVER_POLICY })) |
| 132 | |
| 133 | const registered = Fixtures.toolsRegistered(on) |
| 134 | |
| 135 | await $.session.start(Fixtures.SESSION) |
| 136 | |
| 137 | expect(registered).toEqual(['corp']) |
| 138 | }, |
| 139 | ) |
| 140 | |
| 141 | test( |
| 142 | 'the organization tools are listed as its tiers listed them', |
| 143 | { plugins: [Fixtures.relabeling, Fixtures.listing] }, |
| 144 | async ($, on) => { |
| 145 | on('settings.read', () => ({ value: Fixtures.ALLOWLIST })) |
| 146 | on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) |
| 147 | |
| 148 | const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) |
| 149 | |
| 150 | expect(text?.split('\n')).toEqual([ |
| 151 | 'mcp__corp__search: Searches the corp wiki.', |
| 152 | 'Bash: relabeled', |
| 153 | ]) |
| 154 | }, |
| 155 | ) |
| 156 | |
| 157 | test( |
| 158 | "a seated plugin's own server tools are listed as its tiers listed them", |
| 159 | { plugins: [Fixtures.relabeling, Fixtures.listing] }, |
| 160 | async ($, on) => { |
| 161 | on('settings.read', () => ({ value: Fixtures.SEATED_SUITE })) |
| 162 | on('tool.list', () => ({ value: [...Fixtures.SUITE_TOOLS] })) |
| 163 | |
| 164 | const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) |
| 165 | |
| 166 | expect(text?.split('\n')).toEqual([ |
| 167 | 'mcp__plugin_corp-suite_corp-jira__create_ticket: Creates a ticket.', |
| 168 | 'Bash: relabeled', |
| 169 | ]) |
| 170 | }, |
| 171 | ) |
| 172 | |
| 173 | test( |
| 174 | 'a seated plugin listed in another case has its server tools listed too', |
| 175 | { plugins: [Fixtures.relabeling, Fixtures.listing] }, |
| 176 | async ($, on) => { |
| 177 | on('settings.read', () => ({ |
| 178 | value: { prependPlugins: ['Corp-Suite@market'] }, |
| 179 | })) |
| 180 | on('tool.list', () => ({ value: [...Fixtures.SUITE_TOOLS] })) |
| 181 | |
| 182 | const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) |
| 183 | |
| 184 | expect(text?.split('\n')).toEqual([ |
| 185 | 'mcp__plugin_corp-suite_corp-jira__create_ticket: Creates a ticket.', |
| 186 | 'Bash: relabeled', |
| 187 | ]) |
| 188 | }, |
| 189 | ) |
| 190 | |
| 191 | test( |
| 192 | 'a plugin named as one a version-pinned entry turns on adds no tool', |
| 193 | { |
| 194 | plugins: [ |
| 195 | Fixtures.registering('corp-suite'), |
| 196 | Fixtures.registering('mine'), |
| 197 | ], |
| 198 | }, |
| 199 | async ($, on) => { |
| 200 | on('settings.read', () => ({ |
| 201 | value: { enabledPlugins: { 'corp-suite@market': ['^1.0.0'] } }, |
| 202 | })) |
| 203 | |
| 204 | const registered = Fixtures.toolsRegistered(on) |
| 205 | |
| 206 | await $.session.start(Fixtures.SESSION) |
| 207 | |
| 208 | expect(registered).toEqual(['mine']) |
| 209 | }, |
| 210 | ) |
| 211 | |
| 212 | test( |
| 213 | 'a server policy delivers itself counts as a tool policy, unlisted', |
| 214 | { plugins: [Fixtures.relabeling, Fixtures.listing] }, |
| 215 | async ($, on) => { |
| 216 | on('settings.read', () => ({ value: Fixtures.SERVER_POLICY })) |
| 217 | on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) |
| 218 | |
| 219 | const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) |
| 220 | |
| 221 | expect(text?.split('\n')).toEqual([ |
| 222 | 'mcp__corp__search: Searches the corp wiki.', |
| 223 | 'Bash: relabeled', |
| 224 | ]) |
| 225 | }, |
| 226 | ) |
| 227 | |
| 228 | test( |
| 229 | "with no policy to read the organization's listing stands whole", |
| 230 | { plugins: [Fixtures.relabeling, Fixtures.listing] }, |
| 231 | async ($, on) => { |
| 232 | const stopPolicy = Fixtures.policyUntilStopped( |
| 233 | on, |
| 234 | Fixtures.ALLOWLIST, |
| 235 | 'settings unreadable', |
| 236 | ) |
| 237 | |
| 238 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 239 | on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) |
| 240 | |
| 241 | await $.prompt.section(Fixtures.MEMORY) |
| 242 | stopPolicy() |
| 243 | |
| 244 | const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) |
| 245 | |
| 246 | expect(text?.split('\n')).toEqual([ |
| 247 | 'mcp__corp__search: Searches the corp wiki.', |
| 248 | 'Bash: Runs a command.', |
| 249 | ]) |
| 250 | }, |
| 251 | ) |
| 252 | |
| 253 | test( |
| 254 | 'a prompt section passes over the plugins the person installed', |
| 255 | { plugins: [Fixtures.dropping, Fixtures.signing] }, |
| 256 | async ($, on) => { |
| 257 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 258 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 259 | |
| 260 | expect(await $.prompt.section(Fixtures.MEMORY)).toEqual({ |
| 261 | text: 'the org says hi (signed)', |
| 262 | }) |
| 263 | }, |
| 264 | ) |
| 265 | |
| 266 | test( |
| 267 | 'what the engine attaches passes over the plugins the person installed', |
| 268 | { plugins: [Fixtures.droppingAttachments] }, |
| 269 | async ($, on) => { |
| 270 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 271 | on('prompt.attachment', ($, e) => ({ text: e.text })) |
| 272 | |
| 273 | expect(await $.prompt.attachment(Fixtures.HOOK_NOTE)).toEqual({ |
| 274 | text: Fixtures.HOOK_NOTE.text, |
| 275 | }) |
| 276 | }, |
| 277 | ) |
| 278 | |
| 279 | test( |
| 280 | 'a row the engine injects is kept whole over the plugins they installed', |
| 281 | { plugins: [Fixtures.blankingRows] }, |
| 282 | async ($, on) => { |
| 283 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 284 | |
| 285 | expect(await $.session.append(Fixtures.HOOK_CONTEXT_ROW)).toMatchObject({ |
| 286 | message: { content: Fixtures.HOOK_CONTEXT_ROW.message.content }, |
| 287 | }) |
| 288 | expect(await $.session.append(Fixtures.REMINDER_ROW)).toMatchObject({ |
| 289 | message: { content: Fixtures.REMINDER_ROW.message.content }, |
| 290 | }) |
| 291 | }, |
| 292 | ) |
| 293 | |
| 294 | test( |
| 295 | 'any other row is theirs to shape as before', |
| 296 | { plugins: [Fixtures.blankingRows] }, |
| 297 | async ($, on) => { |
| 298 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 299 | |
| 300 | expect(await $.session.append(Fixtures.PROMPT_ROW)).not.toMatchObject({ |
| 301 | message: { content: Fixtures.PROMPT_ROW.message.content }, |
| 302 | }) |
| 303 | }, |
| 304 | ) |
| 305 | |
| 306 | test( |
| 307 | "an organization plugin's note is not theirs to blank; their own is", |
| 308 | { |
| 309 | plugins: [ |
| 310 | Fixtures.blankingRows, |
| 311 | Fixtures.noting('mine'), |
| 312 | Fixtures.noting('suite', 'append'), |
| 313 | ], |
| 314 | }, |
| 315 | async ($, on) => { |
| 316 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 317 | on('session.start', ($, e) => ({ cwd: e.cwd })) |
| 318 | |
| 319 | const kept: string[] = [] |
| 320 | |
| 321 | on('ui.log', ($, e, next) => { |
| 322 | kept.push(`${next.origin.plugin} kept ${e.text}`) |
| 323 | |
| 324 | return { value: undefined } |
| 325 | }) |
| 326 | |
| 327 | await $.session.start(Fixtures.SESSION) |
| 328 | |
| 329 | expect(kept.toSorted()).toEqual([ |
| 330 | 'mine kept []', |
| 331 | 'suite kept [{"type":"text","text":"a note"}]', |
| 332 | ]) |
| 333 | }, |
| 334 | ) |
| 335 | |
| 336 | test( |
| 337 | "the system prompt's sections pass over the plugins the person installed", |
| 338 | { plugins: [Fixtures.emptying, Fixtures.heading] }, |
| 339 | async ($, on) => { |
| 340 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 341 | |
| 342 | on('prompt.compose', () => ({ |
| 343 | sections: [{ id: 'body', text: 'the body', scope: 'shared' }], |
| 344 | })) |
| 345 | |
| 346 | expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({ |
| 347 | sections: [ |
| 348 | { id: 'heading:org', text: 'the org says hi', scope: 'shared' }, |
| 349 | { id: 'body', text: 'the body', scope: 'shared' }, |
| 350 | ], |
| 351 | }) |
| 352 | }, |
| 353 | ) |
| 354 | |
| 355 | test( |
| 356 | "nor does a person's plugin drop or reword a section it asked for", |
| 357 | { plugins: [Fixtures.rewording, Fixtures.heading] }, |
| 358 | async ($, on) => { |
| 359 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 360 | |
| 361 | on('prompt.compose', () => ({ |
| 362 | sections: [{ id: 'body', text: 'the body', scope: 'shared' }], |
| 363 | })) |
| 364 | |
| 365 | expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({ |
| 366 | sections: [ |
| 367 | { id: 'heading:org', text: 'the org says hi', scope: 'shared' }, |
| 368 | { id: 'body', text: 'the body', scope: 'shared' }, |
| 369 | ], |
| 370 | }) |
| 371 | }, |
| 372 | ) |
| 373 | |
| 374 | test( |
| 375 | "a user plugin's rewrite of policy is skipped for every other reader", |
| 376 | { |
| 377 | plugins: [ |
| 378 | Fixtures.stripping, |
| 379 | Fixtures.reading, |
| 380 | Fixtures.registering('mine'), |
| 381 | ], |
| 382 | }, |
| 383 | async ($, on) => { |
| 384 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 385 | |
| 386 | const registered = Fixtures.toolsRegistered(on) |
| 387 | const { text } = await $.command.run(Fixtures.POLICY_COMMAND) |
| 388 | |
| 389 | await $.session.start(Fixtures.SESSION) |
| 390 | |
| 391 | expect( |
| 392 | JSON.parse(text ?? 'null'), |
| 393 | "another user plugin's read sees the allowlist the stripper hides", |
| 394 | ).toEqual(Fixtures.MANAGED_POLICY) |
| 395 | |
| 396 | expect( |
| 397 | registered, |
| 398 | "sec-default's own tool.register hook still reads the allowlist", |
| 399 | ).toEqual([]) |
| 400 | }, |
| 401 | ) |
| 402 | |
| 403 | test( |
| 404 | "an organization provider's subject passes over the user plugins", |
| 405 | { plugins: [Fixtures.marking] }, |
| 406 | async ($, on) => { |
| 407 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 408 | Fixtures.subjectsEchoed(on) |
| 409 | |
| 410 | for (const provider of Fixtures.ORG_PROVIDERS) { |
| 411 | expect( |
| 412 | await $.tool.describe( |
| 413 | Fixtures.toolDescribed('mcp__corp__search', provider), |
| 414 | ), |
| 415 | ).toEqual({ description: 'd' }) |
| 416 | |
| 417 | expect( |
| 418 | ( |
| 419 | await $.command.describe( |
| 420 | Fixtures.commandDescribed('suite:deploy', provider), |
| 421 | ) |
| 422 | ).isHidden, |
| 423 | ).toBe(false) |
| 424 | |
| 425 | expect( |
| 426 | await $.agent.offer( |
| 427 | Fixtures.agentOffered('suite:reviewer', provider), |
| 428 | ), |
| 429 | ).toEqual({ isOffered: true }) |
| 430 | |
| 431 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 432 | model: 'core', |
| 433 | }) |
| 434 | } |
| 435 | |
| 436 | for (const provider of Fixtures.USER_REACHABLE_PROVIDERS) { |
| 437 | expect( |
| 438 | await $.tool.describe( |
| 439 | Fixtures.toolDescribed('mcp__mine__search', provider), |
| 440 | ), |
| 441 | ).toEqual({ description: 'user: d' }) |
| 442 | |
| 443 | expect( |
| 444 | ( |
| 445 | await $.command.describe( |
| 446 | Fixtures.commandDescribed('mine:deploy', provider), |
| 447 | ) |
| 448 | ).isHidden, |
| 449 | ).toBe(true) |
| 450 | |
| 451 | expect( |
| 452 | await $.agent.offer(Fixtures.agentOffered('reviewer', provider)), |
| 453 | ).toEqual({ isOffered: false }) |
| 454 | |
| 455 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 456 | model: 'user', |
| 457 | }) |
| 458 | } |
| 459 | }, |
| 460 | ) |
| 461 | |
| 462 | test( |
| 463 | 'an odd provider passes over the user plugins: it fails closed', |
| 464 | { plugins: [Fixtures.marking] }, |
| 465 | async ($, on) => { |
| 466 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 467 | Fixtures.subjectsEchoed(on) |
| 468 | |
| 469 | for (const provider of Fixtures.ODD_PROVIDERS) { |
| 470 | expect( |
| 471 | await $.tool.describe(Fixtures.toolDescribed('Bash', provider)), |
| 472 | ).toEqual({ description: 'd' }) |
| 473 | |
| 474 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 475 | model: 'core', |
| 476 | }) |
| 477 | } |
| 478 | }, |
| 479 | ) |
| 480 | |
| 481 | test( |
| 482 | 'a subject decision reads no policy; a burst of listings reads once', |
| 483 | { plugins: [Fixtures.listing] }, |
| 484 | async ($, on) => { |
| 485 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 486 | |
| 487 | Fixtures.subjectsEchoed(on) |
| 488 | on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) |
| 489 | |
| 490 | for (const provider of Fixtures.ORG_PROVIDERS) { |
| 491 | await $.tool.describe(Fixtures.toolDescribed('mcp__corp__x', provider)) |
| 492 | await $.agent.spawn(Fixtures.agentSpawned(provider)) |
| 493 | } |
| 494 | |
| 495 | expect(reads(), "one read: the person's plugin was admitted").toBe(1) |
| 496 | |
| 497 | await Promise.all([ |
| 498 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 499 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 500 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 501 | ]) |
| 502 | |
| 503 | expect(reads()).toBe(2) |
| 504 | }, |
| 505 | ) |
| 506 | |
| 507 | test( |
| 508 | 'the refusal a user-tier caller reads names the allowlist', |
| 509 | { |
| 510 | plugins: [ |
| 511 | { |
| 512 | name: 'asking', |
| 513 | register(on) { |
| 514 | on('command.run', { command: 'greet' }, $ => |
| 515 | $.tool |
| 516 | .register({ |
| 517 | name: 'greet', |
| 518 | description: 'Says hello.', |
| 519 | inputSchema: { type: 'object' }, |
| 520 | }) |
| 521 | .then( |
| 522 | () => ({ text: 'registered' }), |
| 523 | (error: unknown) => ({ text: String(error) }), |
| 524 | ), |
| 525 | ) |
| 526 | }, |
| 527 | }, |
| 528 | ], |
| 529 | }, |
| 530 | async ($, on) => { |
| 531 | on('settings.read', () => ({ value: Fixtures.ALLOWLIST })) |
| 532 | |
| 533 | const { text } = await $.command.run({ |
| 534 | command: 'greet', |
| 535 | args: '', |
| 536 | origin: { kind: 'composer' }, |
| 537 | presentation: Fixtures.FULLSCREEN, |
| 538 | }) |
| 539 | |
| 540 | expect(text).toEndWith( |
| 541 | `asking: $.tool.register: ${Hooks.TOOL_REGISTER_REFUSAL}`, |
| 542 | ) |
| 543 | }, |
| 544 | ) |
| 545 | |
| 546 | test( |
| 547 | "under allowManagedModsOnly a person's mod is kept out, told why", |
| 548 | { plugins: [Fixtures.registering('mine')] }, |
| 549 | async ($, on) => { |
| 550 | on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY })) |
| 551 | |
| 552 | const registered = Fixtures.toolsRegistered(on) |
| 553 | |
| 554 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 555 | Hooks.managedModsOnlyRefusal('mine'), |
| 556 | ) |
| 557 | |
| 558 | expect(registered, 'no hook of it ever ran').toEqual([]) |
| 559 | }, |
| 560 | ) |
| 561 | |
| 562 | test( |
| 563 | "under it the organization's mods and the built-ins load", |
| 564 | { |
| 565 | plugins: [ |
| 566 | Fixtures.registering('suite', 'prepend'), |
| 567 | Fixtures.registering('after', 'append'), |
| 568 | Fixtures.registering('bundled', 'builtin'), |
| 569 | ], |
| 570 | }, |
| 571 | async ($, on) => { |
| 572 | on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY })) |
| 573 | |
| 574 | const registered = Fixtures.toolsRegistered(on) |
| 575 | |
| 576 | await $.session.start(Fixtures.SESSION) |
| 577 | |
| 578 | expect(registered).toEqual(['suite', 'after', 'bundled']) |
| 579 | }, |
| 580 | ) |
| 581 | |
| 582 | test( |
| 583 | 'set to false, a mod the person installed loads as before', |
| 584 | { plugins: [Fixtures.registering('mine')] }, |
| 585 | async ($, on) => { |
| 586 | on('settings.read', () => ({ value: Fixtures.modsPolicyOf(false) })) |
| 587 | |
| 588 | const registered = Fixtures.toolsRegistered(on) |
| 589 | |
| 590 | await $.session.start(Fixtures.SESSION) |
| 591 | |
| 592 | expect(registered).toEqual(['mine']) |
| 593 | }, |
| 594 | ) |
| 595 | |
| 596 | test( |
| 597 | "a person's copy under an organization mod's name is still theirs", |
| 598 | { plugins: [Fixtures.registering('suite')] }, |
| 599 | async ($, on) => { |
| 600 | on('settings.read', () => ({ |
| 601 | value: { ...Fixtures.MANAGED_POLICY, ...Fixtures.MANAGED_MODS_ONLY }, |
| 602 | })) |
| 603 | |
| 604 | const registered = Fixtures.toolsRegistered(on) |
| 605 | |
| 606 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 607 | Hooks.managedModsOnlyRefusal('suite'), |
| 608 | ) |
| 609 | |
| 610 | expect(registered).toEqual([]) |
| 611 | }, |
| 612 | ) |
| 613 | |
| 614 | test( |
| 615 | "only managed settings are asked: a person's own cannot loosen it", |
| 616 | { plugins: [Fixtures.registering('mine')] }, |
| 617 | async ($, on) => { |
| 618 | Fixtures.policyBySource( |
| 619 | on, |
| 620 | Fixtures.MANAGED_MODS_ONLY, |
| 621 | Fixtures.modsPolicyOf(false), |
| 622 | ) |
| 623 | |
| 624 | const registered = Fixtures.toolsRegistered(on) |
| 625 | |
| 626 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 627 | Hooks.managedModsOnlyRefusal('mine'), |
| 628 | ) |
| 629 | |
| 630 | expect(registered).toEqual([]) |
| 631 | }, |
| 632 | ) |
| 633 | |
| 634 | test( |
| 635 | "nor turn it on: set only in a person's settings, it is not read", |
| 636 | { plugins: [Fixtures.registering('mine')] }, |
| 637 | async ($, on) => { |
| 638 | Fixtures.policyBySource( |
| 639 | on, |
| 640 | Fixtures.NO_ALLOWLIST, |
| 641 | Fixtures.MANAGED_MODS_ONLY, |
| 642 | ) |
| 643 | |
| 644 | const registered = Fixtures.toolsRegistered(on) |
| 645 | |
| 646 | await $.session.start(Fixtures.SESSION) |
| 647 | |
| 648 | expect(registered).toEqual(['mine']) |
| 649 | }, |
| 650 | ) |
| 651 | |
| 652 | test( |
| 653 | "a policy that cannot be read keeps a person's mod out: the hook's catch", |
| 654 | { plugins: [Fixtures.registering('mine')] }, |
| 655 | async ($, on) => { |
| 656 | on('settings.read', () => ({ deny: 'managed settings unreadable' })) |
| 657 | |
| 658 | const lines = Fixtures.logged(on) |
| 659 | const registered = Fixtures.toolsRegistered(on) |
| 660 | |
| 661 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 662 | Hooks.managedModsOnlyRefusal('mine'), |
| 663 | ) |
| 664 | |
| 665 | expect({ registered, lines }).toEqual({ |
| 666 | registered: [], |
| 667 | lines: [ |
| 668 | 'debug: plugin.register hook failed judging mine (throw): ' + |
| 669 | 'sec-default: $.settings.read: managed settings unreadable', |
| 670 | ], |
| 671 | }) |
| 672 | }, |
| 673 | ) |
| 674 | |
| 675 | test( |
| 676 | 'a deny rule holds over an allow from a plugin the person installed', |
| 677 | { plugins: [Fixtures.allowing('easy')] }, |
| 678 | async ($, on) => { |
| 679 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 680 | |
| 681 | const lines = Fixtures.logged(on) |
| 682 | |
| 683 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 684 | |
| 685 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 686 | |
| 687 | expect(lines).toEqual([ |
| 688 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.RULE_DENY, 'lift')}`, |
| 689 | ]) |
| 690 | }, |
| 691 | ) |
| 692 | |
| 693 | test( |
| 694 | 'two plugins of the person chained are each named once in a session', |
| 695 | { |
| 696 | plugins: [Fixtures.allowing('first'), Fixtures.asking('second')], |
| 697 | }, |
| 698 | async ($, on) => { |
| 699 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 700 | |
| 701 | const lines = Fixtures.logged(on) |
| 702 | |
| 703 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 704 | |
| 705 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 706 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 707 | |
| 708 | expect(lines.toSorted()).toEqual([ |
| 709 | `transcript: ${Hooks.heldNotice('first', 'Bash', Fixtures.RULE_DENY, 'lift')}`, |
| 710 | `transcript: ${Hooks.heldNotice('second', 'Bash', Fixtures.RULE_DENY, 'lift')}`, |
| 711 | ]) |
| 712 | }, |
| 713 | ) |
| 714 | |
| 715 | test( |
| 716 | 'a deny rule turned into an ask by a plugin of the person holds too', |
| 717 | { plugins: [Fixtures.asking('easy')] }, |
| 718 | async ($, on) => { |
| 719 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 720 | Fixtures.logged(on) |
| 721 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 722 | |
| 723 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 724 | }, |
| 725 | ) |
| 726 | |
| 727 | test( |
| 728 | "an organization plugin's allow over a deny rule stands, above or below", |
| 729 | { |
| 730 | plugins: [ |
| 731 | Fixtures.allowing('suite', 'append'), |
| 732 | Fixtures.allowing('easy'), |
| 733 | ], |
| 734 | }, |
| 735 | async ($, on) => { |
| 736 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 737 | |
| 738 | const lines = Fixtures.logged(on) |
| 739 | |
| 740 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 741 | |
| 742 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 743 | expect(lines).toEqual([]) |
| 744 | }, |
| 745 | ) |
| 746 | |
| 747 | test( |
| 748 | "a prepended organization plugin's allow over a deny rule stands", |
| 749 | { plugins: [Fixtures.allowing('guard', 'prepend')] }, |
| 750 | async ($, on) => { |
| 751 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 752 | |
| 753 | const lines = Fixtures.logged(on) |
| 754 | |
| 755 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 756 | |
| 757 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 758 | expect(lines).toEqual([]) |
| 759 | }, |
| 760 | ) |
| 761 | |
| 762 | test( |
| 763 | "a built-in's allow over a deny rule stands", |
| 764 | { plugins: [Fixtures.allowing('bundled', 'builtin')] }, |
| 765 | async ($, on) => { |
| 766 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 767 | |
| 768 | const lines = Fixtures.logged(on) |
| 769 | |
| 770 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 771 | |
| 772 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 773 | expect(lines).toEqual([]) |
| 774 | }, |
| 775 | ) |
| 776 | |
| 777 | test( |
| 778 | 'an ask rule holds over an allow from a plugin the person installed', |
| 779 | { plugins: [Fixtures.allowing('easy')] }, |
| 780 | async ($, on) => { |
| 781 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 782 | |
| 783 | const lines = Fixtures.logged(on) |
| 784 | |
| 785 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 786 | |
| 787 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_ASK) |
| 788 | |
| 789 | expect(lines).toEqual([ |
| 790 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.RULE_ASK, 'lift')}`, |
| 791 | ]) |
| 792 | }, |
| 793 | ) |
| 794 | |
| 795 | test( |
| 796 | "a classic hook's ask holds over an allow from a plugin of the person", |
| 797 | { plugins: [Fixtures.allowing('easy')] }, |
| 798 | async ($, on) => { |
| 799 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 800 | |
| 801 | const lines = Fixtures.logged(on) |
| 802 | |
| 803 | Fixtures.checksAnswered(on, Fixtures.HOOK_ASK) |
| 804 | |
| 805 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.HOOK_ASK) |
| 806 | |
| 807 | expect(lines).toEqual([ |
| 808 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.HOOK_ASK, 'lift')}`, |
| 809 | ]) |
| 810 | }, |
| 811 | ) |
| 812 | |
| 813 | test( |
| 814 | "the read block's ask on a shell read holds over their allow", |
| 815 | { plugins: [Fixtures.allowing('easy')] }, |
| 816 | async ($, on) => { |
| 817 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 818 | |
| 819 | const lines = Fixtures.logged(on) |
| 820 | |
| 821 | Fixtures.checksAnswered(on, Fixtures.SETTING_ASK) |
| 822 | |
| 823 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.SETTING_ASK) |
| 824 | |
| 825 | expect(lines).toEqual([ |
| 826 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.SETTING_ASK, 'lift')}`, |
| 827 | ]) |
| 828 | }, |
| 829 | ) |
| 830 | |
| 831 | test( |
| 832 | "the mode's own ask, where a host answers it, holds over their allow", |
| 833 | { plugins: [Fixtures.allowing('easy')] }, |
| 834 | async ($, on) => { |
| 835 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 836 | |
| 837 | const lines = Fixtures.logged(on) |
| 838 | |
| 839 | Fixtures.checksAnswered(on, Fixtures.HOST_ASK) |
| 840 | |
| 841 | expect(await $.tool.check(Fixtures.CHECKED_BY_HOST)).toEqual( |
| 842 | Fixtures.HOST_ASK, |
| 843 | ) |
| 844 | |
| 845 | expect(lines).toEqual([ |
| 846 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.HOST_ASK, 'lift')}`, |
| 847 | ]) |
| 848 | }, |
| 849 | ) |
| 850 | |
| 851 | test( |
| 852 | "an organization plugin's allow over a host's ask stands", |
| 853 | { plugins: [Fixtures.allowing('corp', 'append')] }, |
| 854 | async ($, on) => { |
| 855 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 856 | Fixtures.checksAnswered(on, Fixtures.HOST_ASK) |
| 857 | |
| 858 | expect(await $.tool.check(Fixtures.CHECKED_BY_HOST)).toEqual({ |
| 859 | decision: 'allow', |
| 860 | }) |
| 861 | }, |
| 862 | ) |
| 863 | |
| 864 | test( |
| 865 | "a classic hook's deny holds over their allow", |
| 866 | { plugins: [Fixtures.allowing('easy')] }, |
| 867 | async ($, on) => { |
| 868 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 869 | |
| 870 | const lines = Fixtures.logged(on) |
| 871 | |
| 872 | Fixtures.checksAnswered(on, Fixtures.HOOK_DENY) |
| 873 | |
| 874 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.HOOK_DENY) |
| 875 | |
| 876 | expect(lines).toEqual([ |
| 877 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.HOOK_DENY, 'lift')}`, |
| 878 | ]) |
| 879 | }, |
| 880 | ) |
| 881 | |
| 882 | test( |
| 883 | "a classic hook's deny holds over their ask", |
| 884 | { plugins: [Fixtures.asking('easy')] }, |
| 885 | async ($, on) => { |
| 886 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 887 | Fixtures.logged(on) |
| 888 | Fixtures.checksAnswered(on, Fixtures.HOOK_DENY) |
| 889 | |
| 890 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.HOOK_DENY) |
| 891 | }, |
| 892 | ) |
| 893 | |
| 894 | test( |
| 895 | 'an allow that never called next meets the ask rule all the same', |
| 896 | { plugins: [Fixtures.blindAllowing] }, |
| 897 | async ($, on) => { |
| 898 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 899 | Fixtures.logged(on) |
| 900 | |
| 901 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 902 | |
| 903 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_ASK) |
| 904 | expect(evaluations()).toBe(1) |
| 905 | }, |
| 906 | ) |
| 907 | |
| 908 | test( |
| 909 | "an organization plugin's allow over an ask rule stands", |
| 910 | { |
| 911 | plugins: [ |
| 912 | Fixtures.allowing('suite', 'append'), |
| 913 | Fixtures.allowing('easy'), |
| 914 | ], |
| 915 | }, |
| 916 | async ($, on) => { |
| 917 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 918 | |
| 919 | const lines = Fixtures.logged(on) |
| 920 | |
| 921 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 922 | |
| 923 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 924 | expect(lines).toEqual([]) |
| 925 | }, |
| 926 | ) |
| 927 | |
| 928 | test( |
| 929 | "an ask of theirs over an ask rule is the rule's ask again, whole", |
| 930 | { plugins: [Fixtures.asking('easy')] }, |
| 931 | async ($, on) => { |
| 932 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 933 | |
| 934 | const lines = Fixtures.logged(on) |
| 935 | |
| 936 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 937 | |
| 938 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_ASK) |
| 939 | |
| 940 | expect(lines).toEqual([ |
| 941 | 'transcript: ' + |
| 942 | Hooks.heldNotice('easy', 'Bash', Fixtures.RULE_ASK, 'reword'), |
| 943 | ]) |
| 944 | }, |
| 945 | ) |
| 946 | |
| 947 | test( |
| 948 | "a deny of theirs over a deny rule is the rule's deny again, whole", |
| 949 | { plugins: [Fixtures.tightening] }, |
| 950 | async ($, on) => { |
| 951 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 952 | |
| 953 | const lines = Fixtures.logged(on) |
| 954 | |
| 955 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 956 | |
| 957 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 958 | |
| 959 | expect(lines).toEqual([ |
| 960 | 'transcript: ' + |
| 961 | Hooks.heldNotice('strict', 'Bash', Fixtures.RULE_DENY, 'reword'), |
| 962 | ]) |
| 963 | }, |
| 964 | ) |
| 965 | |
| 966 | test( |
| 967 | 'an ask of theirs that says what it heard costs no second look', |
| 968 | { plugins: [Fixtures.asking('easy')] }, |
| 969 | async ($, on) => { |
| 970 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 971 | |
| 972 | const lines = Fixtures.logged(on) |
| 973 | const evaluations = Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 974 | |
| 975 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ASKED) |
| 976 | expect({ lines, evaluations: evaluations() }).toEqual({ |
| 977 | lines: [], |
| 978 | evaluations: 1, |
| 979 | }) |
| 980 | }, |
| 981 | ) |
| 982 | |
| 983 | test( |
| 984 | 'the option that lets them override deny rules lets them override these', |
| 985 | { plugins: [Fixtures.allowing('easy')] }, |
| 986 | async ($, on) => { |
| 987 | on('settings.read', () => ({ value: Fixtures.overridePolicyOf(true) })) |
| 988 | |
| 989 | const lines = Fixtures.logged(on) |
| 990 | |
| 991 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 992 | |
| 993 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 994 | expect(lines).toEqual([]) |
| 995 | }, |
| 996 | ) |
| 997 | |
| 998 | test( |
| 999 | "the mode's own ask, no rule or hook behind it, is theirs to allow", |
| 1000 | { plugins: [Fixtures.allowing('easy')] }, |
| 1001 | async ($, on) => { |
| 1002 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1003 | |
| 1004 | const lines = Fixtures.logged(on) |
| 1005 | |
| 1006 | Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 1007 | |
| 1008 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 1009 | expect(lines).toEqual([]) |
| 1010 | }, |
| 1011 | ) |
| 1012 | |
| 1013 | test( |
| 1014 | 'a deny holds whatever decided it: it may stand before a rule or a hook', |
| 1015 | { plugins: [Fixtures.allowing('easy')] }, |
| 1016 | async ($, on) => { |
| 1017 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1018 | |
| 1019 | const lines = Fixtures.logged(on) |
| 1020 | |
| 1021 | Fixtures.checksAnswered(on, Fixtures.PLAIN_DENY) |
| 1022 | |
| 1023 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.PLAIN_DENY) |
| 1024 | |
| 1025 | expect(lines).toEqual([ |
| 1026 | `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.PLAIN_DENY, 'lift')}`, |
| 1027 | ]) |
| 1028 | }, |
| 1029 | ) |
| 1030 | |
| 1031 | test( |
| 1032 | 'the notice is no door: a plugin of theirs never runs inside this hook', |
| 1033 | { plugins: [Fixtures.sneaking] }, |
| 1034 | async ($, on) => { |
| 1035 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1036 | |
| 1037 | const lines = Fixtures.logged(on) |
| 1038 | |
| 1039 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1040 | |
| 1041 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1042 | |
| 1043 | expect(lines).toEqual([ |
| 1044 | 'transcript: ' + |
| 1045 | Hooks.heldNotice('sneaking', 'Bash', Fixtures.RULE_DENY, 'lift'), |
| 1046 | ]) |
| 1047 | }, |
| 1048 | ) |
| 1049 | |
| 1050 | test( |
| 1051 | 'nor is the notice theirs to drop', |
| 1052 | { plugins: [Fixtures.muting] }, |
| 1053 | async ($, on) => { |
| 1054 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1055 | |
| 1056 | const lines = Fixtures.logged(on) |
| 1057 | |
| 1058 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 1059 | |
| 1060 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_ASK) |
| 1061 | |
| 1062 | expect(lines).toEqual([ |
| 1063 | `transcript: ${Hooks.heldNotice('muting', 'Bash', Fixtures.RULE_ASK, 'lift')}`, |
| 1064 | ]) |
| 1065 | }, |
| 1066 | ) |
| 1067 | |
| 1068 | test( |
| 1069 | "an organization's line is not theirs to drop; a line of their own is", |
| 1070 | { |
| 1071 | plugins: [ |
| 1072 | Fixtures.muting, |
| 1073 | Fixtures.logging('mine'), |
| 1074 | Fixtures.logging('bundled', 'builtin'), |
| 1075 | Fixtures.logging('suite', 'append'), |
| 1076 | ], |
| 1077 | }, |
| 1078 | async ($, on) => { |
| 1079 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1080 | on('session.start', ($, e) => ({ cwd: e.cwd })) |
| 1081 | |
| 1082 | const heard: string[] = [] |
| 1083 | |
| 1084 | on('ui.log', ($, e, next) => { |
| 1085 | heard.push(next.origin.plugin) |
| 1086 | |
| 1087 | return { value: undefined } |
| 1088 | }) |
| 1089 | |
| 1090 | await $.session.start(Fixtures.SESSION) |
| 1091 | |
| 1092 | expect(heard).toEqual(['suite']) |
| 1093 | }, |
| 1094 | ) |
| 1095 | |
| 1096 | test( |
| 1097 | 'each plugin is told of a kind, by the tool it answered on', |
| 1098 | { plugins: [Fixtures.allowingBash, Fixtures.allowingRead] }, |
| 1099 | async ($, on) => { |
| 1100 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1101 | |
| 1102 | const lines = Fixtures.logged(on) |
| 1103 | |
| 1104 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1105 | |
| 1106 | await $.tool.check(Fixtures.CHECKED) |
| 1107 | await $.tool.check(Fixtures.CHECKED_READ) |
| 1108 | |
| 1109 | expect(lines).toEqual([ |
| 1110 | `transcript: ${Hooks.heldNotice('basher', 'Bash', Fixtures.RULE_DENY, 'lift')}`, |
| 1111 | `transcript: ${Hooks.heldNotice('reader', 'Read', Fixtures.RULE_DENY, 'lift')}`, |
| 1112 | ]) |
| 1113 | }, |
| 1114 | ) |
| 1115 | |
| 1116 | test( |
| 1117 | "an organization's appended plugin has its say in the run past theirs", |
| 1118 | { |
| 1119 | plugins: [Fixtures.allowing('suite', 'append'), Fixtures.blindAllowing], |
| 1120 | }, |
| 1121 | async ($, on) => { |
| 1122 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1123 | |
| 1124 | const lines = Fixtures.logged(on) |
| 1125 | |
| 1126 | Fixtures.checksAnswered(on, Fixtures.RULE_ASK) |
| 1127 | |
| 1128 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 1129 | expect(lines).toEqual([]) |
| 1130 | }, |
| 1131 | ) |
| 1132 | |
| 1133 | test( |
| 1134 | 'a plugin is told once of each kind of thing that held over it', |
| 1135 | { plugins: [Fixtures.allowing('easy')] }, |
| 1136 | async ($, on) => { |
| 1137 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1138 | |
| 1139 | const lines = Fixtures.logged(on) |
| 1140 | const verdicts = [ |
| 1141 | Fixtures.RULE_DENY, |
| 1142 | Fixtures.RULE_ASK, |
| 1143 | Fixtures.RULE_ASK, |
| 1144 | Fixtures.HOOK_ASK, |
| 1145 | Fixtures.PLAIN_DENY, |
| 1146 | Fixtures.RULE_DENY, |
| 1147 | ] |
| 1148 | const turn = Fixtures.checksAnsweredInTurn(on, verdicts) |
| 1149 | |
| 1150 | for (const verdict of verdicts) { |
| 1151 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(verdict) |
| 1152 | turn() |
| 1153 | } |
| 1154 | |
| 1155 | expect(lines).toEqual( |
| 1156 | [ |
| 1157 | Fixtures.RULE_DENY, |
| 1158 | Fixtures.RULE_ASK, |
| 1159 | Fixtures.HOOK_ASK, |
| 1160 | Fixtures.PLAIN_DENY, |
| 1161 | ].map( |
| 1162 | held => |
| 1163 | `transcript: ${Hooks.heldNotice('easy', 'Bash', held, 'lift')}`, |
| 1164 | ), |
| 1165 | ) |
| 1166 | }, |
| 1167 | ) |
| 1168 | |
| 1169 | test( |
| 1170 | 'a plugin of the person that tightens is heard, with one evaluation', |
| 1171 | { plugins: [Fixtures.tightening] }, |
| 1172 | async ($, on) => { |
| 1173 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 1174 | const evaluations = Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 1175 | |
| 1176 | await $.tool.check(Fixtures.CHECKED) |
| 1177 | |
| 1178 | const loaded = { reads: reads(), evaluations: evaluations() } |
| 1179 | |
| 1180 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.PLAIN_DENY) |
| 1181 | |
| 1182 | expect( |
| 1183 | { |
| 1184 | reads: reads() - loaded.reads, |
| 1185 | evaluations: evaluations() - loaded.evaluations, |
| 1186 | }, |
| 1187 | 'once its plugins have loaded, a check reads no policy', |
| 1188 | ).toEqual({ reads: 0, evaluations: 1 }) |
| 1189 | }, |
| 1190 | ) |
| 1191 | |
| 1192 | test( |
| 1193 | "a deny rule holds when an organization's plugin listens above theirs", |
| 1194 | { |
| 1195 | plugins: [ |
| 1196 | Fixtures.listening('audit', 'prepend'), |
| 1197 | Fixtures.allowing('easy'), |
| 1198 | ], |
| 1199 | }, |
| 1200 | async ($, on) => { |
| 1201 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1202 | |
| 1203 | const lines = Fixtures.logged(on) |
| 1204 | |
| 1205 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1206 | |
| 1207 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1208 | |
| 1209 | expect( |
| 1210 | lines.map(line => line.includes('easy')), |
| 1211 | 'one line, naming the plugin of theirs, alone or in its batch', |
| 1212 | ).toEqual([true]) |
| 1213 | }, |
| 1214 | ) |
| 1215 | |
| 1216 | test( |
| 1217 | 'a plugin of the person that only listens costs no second evaluation', |
| 1218 | { plugins: [Fixtures.listening('listening')] }, |
| 1219 | async ($, on) => { |
| 1220 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 1221 | const evaluations = Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 1222 | |
| 1223 | await $.tool.check(Fixtures.CHECKED) |
| 1224 | |
| 1225 | const loaded = { reads: reads(), evaluations: evaluations() } |
| 1226 | |
| 1227 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ASKED) |
| 1228 | |
| 1229 | expect( |
| 1230 | { |
| 1231 | reads: reads() - loaded.reads, |
| 1232 | evaluations: evaluations() - loaded.evaluations, |
| 1233 | }, |
| 1234 | 'once its plugins have loaded, a check reads no policy', |
| 1235 | ).toEqual({ reads: 0, evaluations: 1 }) |
| 1236 | }, |
| 1237 | ) |
| 1238 | |
| 1239 | test('with none of their plugins the verdict passes once', async ($, on) => { |
| 1240 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 1241 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1242 | |
| 1243 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1244 | |
| 1245 | expect({ reads: reads(), evaluations: evaluations() }).toEqual({ |
| 1246 | reads: 0, |
| 1247 | evaluations: 1, |
| 1248 | }) |
| 1249 | }) |
| 1250 | |
| 1251 | test( |
| 1252 | 'an allow that never called next meets the deny rule all the same', |
| 1253 | { plugins: [Fixtures.blindAllowing] }, |
| 1254 | async ($, on) => { |
| 1255 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1256 | |
| 1257 | const lines = Fixtures.logged(on) |
| 1258 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1259 | |
| 1260 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1261 | |
| 1262 | expect({ lines, evaluations: evaluations() }).toEqual({ |
| 1263 | lines: [ |
| 1264 | 'transcript: ' + |
| 1265 | Hooks.heldNotice('blind', 'Bash', Fixtures.RULE_DENY, 'lift'), |
| 1266 | ], |
| 1267 | evaluations: 1, |
| 1268 | }) |
| 1269 | }, |
| 1270 | ) |
| 1271 | |
| 1272 | test( |
| 1273 | 'a rule a plugin of the person writes into its answer is never read', |
| 1274 | { plugins: [Fixtures.forging] }, |
| 1275 | async ($, on) => { |
| 1276 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1277 | |
| 1278 | const lines = Fixtures.logged(on) |
| 1279 | |
| 1280 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1281 | |
| 1282 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1283 | |
| 1284 | expect(lines).toEqual([ |
| 1285 | 'transcript: ' + |
| 1286 | Hooks.heldNotice('forging', 'Bash', Fixtures.RULE_DENY, 'lift'), |
| 1287 | ]) |
| 1288 | }, |
| 1289 | ) |
| 1290 | |
| 1291 | test( |
| 1292 | 'a plugin of the person asking about another command is left out', |
| 1293 | { plugins: [Fixtures.rewriting] }, |
| 1294 | async ($, on) => { |
| 1295 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1296 | Fixtures.logged(on) |
| 1297 | |
| 1298 | const asked: unknown[] = [] |
| 1299 | |
| 1300 | on('tool.check', ($, e) => { |
| 1301 | asked.push(e.input) |
| 1302 | |
| 1303 | return Fixtures.RULE_DENY |
| 1304 | }) |
| 1305 | |
| 1306 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1307 | |
| 1308 | expect( |
| 1309 | asked, |
| 1310 | 'the rules were asked about the command that would run, only', |
| 1311 | ).toEqual([Fixtures.CHECKED.input]) |
| 1312 | }, |
| 1313 | ) |
| 1314 | |
| 1315 | test( |
| 1316 | 'managed settings may let the plugins a person installs override', |
| 1317 | { plugins: [Fixtures.allowing('easy')] }, |
| 1318 | async ($, on) => { |
| 1319 | on('settings.read', () => ({ value: Fixtures.overridePolicyOf(true) })) |
| 1320 | |
| 1321 | const lines = Fixtures.logged(on) |
| 1322 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1323 | |
| 1324 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 1325 | |
| 1326 | expect({ lines, evaluations: evaluations() }).toEqual({ |
| 1327 | lines: [], |
| 1328 | evaluations: 1, |
| 1329 | }) |
| 1330 | }, |
| 1331 | ) |
| 1332 | |
| 1333 | test( |
| 1334 | "only managed settings are asked: a person's own cannot lift a deny rule", |
| 1335 | { plugins: [Fixtures.allowing('easy')] }, |
| 1336 | async ($, on) => { |
| 1337 | Fixtures.policyBySource( |
| 1338 | on, |
| 1339 | Fixtures.MANAGED_POLICY, |
| 1340 | Fixtures.overridePolicyOf(true), |
| 1341 | ) |
| 1342 | |
| 1343 | Fixtures.logged(on) |
| 1344 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1345 | |
| 1346 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1347 | }, |
| 1348 | ) |
| 1349 | |
| 1350 | test( |
| 1351 | 'the option mistyped lifts nothing: only the literal true does', |
| 1352 | { plugins: [Fixtures.allowing('easy')] }, |
| 1353 | async ($, on) => { |
| 1354 | on('settings.read', () => ({ value: Fixtures.overridePolicyOf('true') })) |
| 1355 | Fixtures.logged(on) |
| 1356 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1357 | |
| 1358 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 1359 | }, |
| 1360 | ) |
| 1361 | |
| 1362 | test( |
| 1363 | 'with a policy that cannot be read the deny rule holds: fails closed', |
| 1364 | { plugins: [Fixtures.allowing('easy')] }, |
| 1365 | async ($, on) => { |
| 1366 | const stopPolicy = Fixtures.policyUntilStopped( |
| 1367 | on, |
| 1368 | Fixtures.overridePolicyOf(true), |
| 1369 | 'managed settings unreadable', |
| 1370 | ) |
| 1371 | |
| 1372 | Fixtures.logged(on) |
| 1373 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 1374 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 1375 | |
| 1376 | await $.prompt.section(Fixtures.MEMORY) |
| 1377 | stopPolicy() |
| 1378 | |
| 1379 | expect( |
| 1380 | await $.tool.check(Fixtures.CHECKED), |
| 1381 | 'the policy that read let plugins override; unread, the rule holds', |
| 1382 | ).toEqual(Fixtures.RULE_DENY) |
| 1383 | }, |
| 1384 | ) |
| 1385 | |
| 1386 | test( |
| 1387 | "when the rules cannot be evaluated the call is refused: the hook's catch", |
| 1388 | { plugins: [Fixtures.blindAllowing] }, |
| 1389 | async ($, on) => { |
| 1390 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 1391 | Fixtures.logged(on) |
| 1392 | |
| 1393 | on('tool.check', () => { |
| 1394 | throw new Error('the evaluation failed') |
| 1395 | }) |
| 1396 | |
| 1397 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Hooks.UNCHECKED_DENY) |
| 1398 | }, |
| 1399 | ) |
| 1400 | |
| 1401 | test('the refusal of a pinned variable names the setting and it', () => { |
| 1402 | expect(Hooks.pinnedVariableRefusal('CORP_PROXY')).toBe( |
| 1403 | 'env (managed): CORP_PROXY is not for plugins outside policy to change', |
| 1404 | ) |
| 1405 | }) |
| 1406 | |
| 1407 | test('a name managed env sets is pinned, in any case; no other is', () => { |
| 1408 | expect( |
| 1409 | ['CORP_PROXY', 'corp_proxy', 'Corp_Proxy', 'CORP', 'CORP_PROXY_2'].map( |
| 1410 | name => Hooks.isPinnedVariable(Fixtures.PINNING_POLICY, name), |
| 1411 | ), |
| 1412 | ).toEqual([true, true, true, false, false]) |
| 1413 | }) |
| 1414 | |
| 1415 | test('whichever case managed env itself spells it in', () => { |
| 1416 | expect( |
| 1417 | ['CORP_PROXY', 'corp_proxy', 'CORP'].map(name => |
| 1418 | Hooks.isPinnedVariable(Fixtures.LOWER_CASE_PINNING_POLICY, name), |
| 1419 | ), |
| 1420 | ).toEqual([true, true, false]) |
| 1421 | }) |
| 1422 | |
| 1423 | test('a name managed env sets to nothing is pinned all the same', () => { |
| 1424 | expect( |
| 1425 | Hooks.isPinnedVariable({ env: { CORP_PROXY: '' } }, 'CORP_PROXY'), |
| 1426 | ).toBe(true) |
| 1427 | }) |
| 1428 | |
| 1429 | test('settings with no env pin nothing', () => { |
| 1430 | expect( |
| 1431 | [Fixtures.MANAGED_POLICY, {}, { env: {} }].map(policy => |
| 1432 | Hooks.isPinnedVariable(policy, 'CORP_PROXY'), |
| 1433 | ), |
| 1434 | ).toEqual([false, false, false]) |
| 1435 | }) |
| 1436 | |
| 1437 | test( |
| 1438 | 'a variable managed settings pin is not for a plugin of the person to set', |
| 1439 | { |
| 1440 | plugins: [ |
| 1441 | Fixtures.settingProxy('suite', 'prepend'), |
| 1442 | Fixtures.settingProxy('mine'), |
| 1443 | Fixtures.settingProxy('bundled', 'builtin'), |
| 1444 | ], |
| 1445 | }, |
| 1446 | async ($, on) => { |
| 1447 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1448 | |
| 1449 | const lines = Fixtures.logged(on) |
| 1450 | const set = Fixtures.variablesSet(on) |
| 1451 | |
| 1452 | await $.session.start(Fixtures.SESSION) |
| 1453 | |
| 1454 | expect(set).toEqual(['suite CORP_PROXY=mine', 'bundled CORP_PROXY=mine']) |
| 1455 | expect(lines).toHaveLength(1) |
| 1456 | expect(lines[0]).toContain(Hooks.pinnedVariableRefusal('CORP_PROXY')) |
| 1457 | }, |
| 1458 | ) |
| 1459 | |
| 1460 | test( |
| 1461 | 'nor to unset', |
| 1462 | { plugins: [Fixtures.unsettingProxy] }, |
| 1463 | async ($, on) => { |
| 1464 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1465 | |
| 1466 | const set = Fixtures.variablesSet(on) |
| 1467 | |
| 1468 | await $.session.start(Fixtures.SESSION) |
| 1469 | |
| 1470 | expect(set).toEqual([]) |
| 1471 | }, |
| 1472 | ) |
| 1473 | |
| 1474 | test( |
| 1475 | 'nor under another case, which some systems read as the same name', |
| 1476 | { plugins: [Fixtures.settingLowerCase] }, |
| 1477 | async ($, on) => { |
| 1478 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1479 | Fixtures.logged(on) |
| 1480 | |
| 1481 | const set = Fixtures.variablesSet(on) |
| 1482 | |
| 1483 | await $.session.start(Fixtures.SESSION) |
| 1484 | |
| 1485 | expect(set).toEqual([]) |
| 1486 | }, |
| 1487 | ) |
| 1488 | |
| 1489 | test( |
| 1490 | "what an organization's plugin reads is not theirs to answer", |
| 1491 | { |
| 1492 | plugins: [ |
| 1493 | Fixtures.answeringReads, |
| 1494 | Fixtures.readingProxy('mine'), |
| 1495 | Fixtures.readingProxy('suite', 'append'), |
| 1496 | Fixtures.readingProxy('guard', 'prepend'), |
| 1497 | ], |
| 1498 | }, |
| 1499 | async ($, on) => { |
| 1500 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1501 | on('session.start', ($, e) => ({ cwd: e.cwd })) |
| 1502 | on('env.get', () => ({ value: 'as the engine holds it' })) |
| 1503 | |
| 1504 | const lines = Fixtures.loggedBy(on) |
| 1505 | |
| 1506 | await $.session.start(Fixtures.SESSION) |
| 1507 | |
| 1508 | expect(lines.toSorted()).toEqual([ |
| 1509 | 'guard read as the engine holds it', |
| 1510 | 'mine read theirs', |
| 1511 | 'suite read as the engine holds it', |
| 1512 | ]) |
| 1513 | }, |
| 1514 | ) |
| 1515 | |
| 1516 | test( |
| 1517 | "nor to rewrite what an organization's plugin sets, pinned or not", |
| 1518 | { |
| 1519 | plugins: [ |
| 1520 | Fixtures.revaluing, |
| 1521 | Fixtures.settingProxy('suite', 'append'), |
| 1522 | Fixtures.settingOther('audit', 'append'), |
| 1523 | Fixtures.settingOther('guard', 'prepend'), |
| 1524 | ], |
| 1525 | }, |
| 1526 | async ($, on) => { |
| 1527 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1528 | |
| 1529 | const set = Fixtures.variablesSet(on) |
| 1530 | |
| 1531 | await $.session.start(Fixtures.SESSION) |
| 1532 | |
| 1533 | expect(set.toSorted()).toEqual([ |
| 1534 | 'audit OTHER=mine', |
| 1535 | 'guard OTHER=mine', |
| 1536 | 'suite CORP_PROXY=mine', |
| 1537 | ]) |
| 1538 | }, |
| 1539 | ) |
| 1540 | |
| 1541 | test( |
| 1542 | "an organization's write waits on no policy read, so none can fail it", |
| 1543 | { |
| 1544 | plugins: [Fixtures.revaluing, Fixtures.settingOther('audit', 'append')], |
| 1545 | }, |
| 1546 | async ($, on) => { |
| 1547 | const stopPolicy = Fixtures.policyUntilStopped( |
| 1548 | on, |
| 1549 | Fixtures.MANAGED_POLICY, |
| 1550 | 'managed settings unreadable', |
| 1551 | ) |
| 1552 | |
| 1553 | Fixtures.logged(on) |
| 1554 | |
| 1555 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 1556 | |
| 1557 | const set = Fixtures.variablesSet(on) |
| 1558 | |
| 1559 | await $.prompt.section(Fixtures.MEMORY) |
| 1560 | stopPolicy() |
| 1561 | await $.session.start(Fixtures.SESSION) |
| 1562 | |
| 1563 | expect(set).toEqual(['audit OTHER=mine']) |
| 1564 | }, |
| 1565 | ) |
| 1566 | |
| 1567 | test( |
| 1568 | "a built-in's write of a variable nobody pins still passes their hooks", |
| 1569 | { |
| 1570 | plugins: [ |
| 1571 | Fixtures.revaluing, |
| 1572 | Fixtures.settingOther('bundled', 'builtin'), |
| 1573 | ], |
| 1574 | }, |
| 1575 | async ($, on) => { |
| 1576 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1577 | |
| 1578 | const set = Fixtures.variablesSet(on) |
| 1579 | |
| 1580 | await $.session.start(Fixtures.SESSION) |
| 1581 | |
| 1582 | expect(set).toEqual(['bundled OTHER=theirs']) |
| 1583 | }, |
| 1584 | ) |
| 1585 | |
| 1586 | test( |
| 1587 | 'the option that lets them override deny rules unpins nothing', |
| 1588 | { plugins: [Fixtures.settingProxy('mine')] }, |
| 1589 | async ($, on) => { |
| 1590 | on('settings.read', () => ({ |
| 1591 | value: { |
| 1592 | ...Fixtures.overridePolicyOf(true), |
| 1593 | ...Fixtures.PINNING_POLICY, |
| 1594 | }, |
| 1595 | })) |
| 1596 | Fixtures.logged(on) |
| 1597 | |
| 1598 | const set = Fixtures.variablesSet(on) |
| 1599 | |
| 1600 | await $.session.start(Fixtures.SESSION) |
| 1601 | |
| 1602 | expect(set).toEqual([]) |
| 1603 | }, |
| 1604 | ) |
| 1605 | |
| 1606 | test( |
| 1607 | 'a variable managed settings do not pin is theirs to set', |
| 1608 | { plugins: [Fixtures.settingOther('mine')] }, |
| 1609 | async ($, on) => { |
| 1610 | on('settings.read', () => ({ value: Fixtures.PINNING_POLICY })) |
| 1611 | |
| 1612 | const set = Fixtures.variablesSet(on) |
| 1613 | |
| 1614 | await $.session.start(Fixtures.SESSION) |
| 1615 | |
| 1616 | expect(set).toEqual(['mine OTHER=mine']) |
| 1617 | }, |
| 1618 | ) |
| 1619 | |
| 1620 | test( |
| 1621 | "only managed settings pin: a person's own env binds no plugin", |
| 1622 | { plugins: [Fixtures.settingProxy('mine')] }, |
| 1623 | async ($, on) => { |
| 1624 | Fixtures.policyBySource( |
| 1625 | on, |
| 1626 | Fixtures.MANAGED_POLICY, |
| 1627 | Fixtures.PINNING_POLICY, |
| 1628 | ) |
| 1629 | |
| 1630 | const set = Fixtures.variablesSet(on) |
| 1631 | |
| 1632 | await $.session.start(Fixtures.SESSION) |
| 1633 | |
| 1634 | expect(set).toEqual(['mine CORP_PROXY=mine']) |
| 1635 | }, |
| 1636 | ) |
| 1637 | |
| 1638 | test( |
| 1639 | 'with a policy that cannot be read they set no variable: fails closed', |
| 1640 | { plugins: [Fixtures.settingOther('mine')] }, |
| 1641 | async ($, on) => { |
| 1642 | const stopPolicy = Fixtures.policyUntilStopped( |
| 1643 | on, |
| 1644 | Fixtures.MANAGED_POLICY, |
| 1645 | 'managed settings unreadable', |
| 1646 | ) |
| 1647 | |
| 1648 | Fixtures.logged(on) |
| 1649 | |
| 1650 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 1651 | |
| 1652 | const set = Fixtures.variablesSet(on) |
| 1653 | |
| 1654 | await $.prompt.section(Fixtures.MEMORY) |
| 1655 | stopPolicy() |
| 1656 | await $.session.start(Fixtures.SESSION) |
| 1657 | |
| 1658 | expect(set).toEqual([]) |
| 1659 | }, |
| 1660 | ) |
| 1661 | |
| 1662 | test("a server's own name, and any name under its tools' prefix", () => { |
| 1663 | expect( |
| 1664 | ['corp', 'corp__x', 'corp__x__y', 'corp._x'].map(plugin => |
| 1665 | Hooks.namesOrgTool(Fixtures.SERVER_POLICY, plugin, 'greet'), |
| 1666 | ), |
| 1667 | ).toEqual([true, true, true, true]) |
| 1668 | }) |
| 1669 | |
| 1670 | test('a name that only begins like one is no server of theirs', () => { |
| 1671 | expect( |
| 1672 | ['corporate', 'corpx', 'x__corp', 'x-corp', 'mine'].map(plugin => |
| 1673 | Hooks.namesOrgTool(Fixtures.SERVER_POLICY, plugin, 'greet'), |
| 1674 | ), |
| 1675 | ).toEqual([false, false, false, false, false]) |
| 1676 | }) |
| 1677 | |
| 1678 | test('another case or other separators spell the same server', () => { |
| 1679 | expect( |
| 1680 | ['Corp', 'CORP', 'corp_x', 'corp-x', 'corp.x', '_corp_'].map(plugin => |
| 1681 | Hooks.namesOrgTool(Fixtures.SERVER_POLICY, plugin, 'greet'), |
| 1682 | ), |
| 1683 | ).toEqual([true, true, true, true, true, true]) |
| 1684 | }) |
| 1685 | |
| 1686 | test('a claude.ai server with a run of spaces folds as its tools do', () => { |
| 1687 | expect( |
| 1688 | ['mcp__claude_ai_tools__x', 'mcp__claude_ai__tools__x'].map(tool => |
| 1689 | Hooks.isOrgTool( |
| 1690 | { managedMcpServers: { 'claude.ai tools': {} } }, |
| 1691 | tool, |
| 1692 | ), |
| 1693 | ), |
| 1694 | ).toEqual([true, true]) |
| 1695 | }) |
| 1696 | |
| 1697 | test('a plugin and its tool that spell a server together count too', () => { |
| 1698 | expect( |
| 1699 | [ |
| 1700 | ['corp', 'prod__deploy'], |
| 1701 | ['corp', 'deploy'], |
| 1702 | ['corp_tools', 'greet'], |
| 1703 | ['corp', 'tools__greet'], |
| 1704 | ].map(([plugin = '', tool = '']) => |
| 1705 | Hooks.namesOrgTool( |
| 1706 | { managedMcpServers: { corp__prod: {}, 'corp.tools': {} } }, |
| 1707 | plugin, |
| 1708 | tool, |
| 1709 | ), |
| 1710 | ), |
| 1711 | ).toEqual([true, false, true, true]) |
| 1712 | }) |
| 1713 | |
| 1714 | test("a seated plugin's own servers are the organization's", () => { |
| 1715 | expect([ |
| 1716 | Hooks.namesOrgTool( |
| 1717 | Fixtures.SEATED_SUITE, |
| 1718 | 'plugin_corp-suite_corp-jira', |
| 1719 | 'create_ticket', |
| 1720 | ), |
| 1721 | Hooks.namesOrgTool(Fixtures.SEATED_SUITE, 'Plugin.Corp_Suite.x', 'y'), |
| 1722 | Hooks.isOrgTool( |
| 1723 | Fixtures.SEATED_SUITE, |
| 1724 | 'mcp__plugin_corp-suite_corp-jira__create_ticket', |
| 1725 | ), |
| 1726 | ]).toEqual([true, true, true]) |
| 1727 | }) |
| 1728 | |
| 1729 | test('a seated plugin is told in any case, as the engine seats it', () => { |
| 1730 | const listed = { prependPlugins: ['Corp-Suite@market'] } |
| 1731 | |
| 1732 | expect([ |
| 1733 | Hooks.isOrgTool(listed, 'mcp__plugin_corp-suite_corp-jira__x'), |
| 1734 | Hooks.isOrgTool(listed, 'mcp__corp-suite__greet'), |
| 1735 | Hooks.isOrgTool(Fixtures.SEATED_SUITE, 'mcp__plugin_Corp-Suite_j__x'), |
| 1736 | Hooks.isOrgTool(listed, 'mcp__plugin_corp-suites_j__x'), |
| 1737 | Hooks.isOrgTool(Fixtures.SERVER_POLICY, 'mcp__Corp__search'), |
| 1738 | ]).toEqual([true, true, true, false, false]) |
| 1739 | }) |
| 1740 | |
| 1741 | test('an entry that pins versions turns its plugin on, as true does', () => { |
| 1742 | const pinned = { enabledPlugins: { 'corp-suite@market': ['^1.0.0'] } } |
| 1743 | const off = { enabledPlugins: { 'corp-suite@market': false } } |
| 1744 | |
| 1745 | expect([ |
| 1746 | Hooks.namesOrgTool(pinned, 'corp-suite', 'greet'), |
| 1747 | Hooks.isOrgTool(pinned, 'mcp__corp-suite__greet'), |
| 1748 | Hooks.namesOrgTool(off, 'corp-suite', 'greet'), |
| 1749 | Hooks.namesOrgTool(pinned, 'mine', 'greet'), |
| 1750 | ]).toEqual([true, true, false, false]) |
| 1751 | }) |
| 1752 | |
| 1753 | test('a name in the other normalization form is the same name', () => { |
| 1754 | const policy = { prependPlugins: ['caf\u00e9-suite@market'] } |
| 1755 | |
| 1756 | expect([ |
| 1757 | Hooks.namesOrgTool(policy, 'cafe\u0301-suite', 'x'), |
| 1758 | Hooks.isOrgTool(policy, 'mcp__plugin_cafe\u0301-suite_jira__x'), |
| 1759 | Hooks.isOrgTool(policy, 'mcp__cafe\u0301-suite__x'), |
| 1760 | ]).toEqual([true, true, true]) |
| 1761 | }) |
| 1762 | |
| 1763 | test('only the id managed settings turn on keeps its own name', () => { |
| 1764 | const policy = { |
| 1765 | enabledPlugins: { 'corp-guard@corp-market': true }, |
| 1766 | appendPlugins: ['corp-guard@corp-market'], |
| 1767 | } |
| 1768 | const asked = (id?: string) => |
| 1769 | Hooks.namesOrgTool(policy, 'corp-guard', 'x', id) |
| 1770 | |
| 1771 | expect([ |
| 1772 | asked('corp-guard@corp-market'), |
| 1773 | asked('Corp-Guard@Corp-Market'), |
| 1774 | asked('corp-guard@inline'), |
| 1775 | asked('corp-guard@elsewhere'), |
| 1776 | asked('corp_guard@corp-market'), |
| 1777 | asked(), |
| 1778 | Hooks.namesOrgTool( |
| 1779 | { ...policy, managedMcpServersUnreadable: true }, |
| 1780 | 'corp-guard', |
| 1781 | 'x', |
| 1782 | 'corp-guard@corp-market', |
| 1783 | ), |
| 1784 | Hooks.namesOrgTool({}, 'corp-guard', 'x', 'corp-guard@inline'), |
| 1785 | ]).toEqual([false, false, true, true, true, true, true, false]) |
| 1786 | }) |
| 1787 | |
| 1788 | test('an accented name is told as the engine spells it in a tool', () => { |
| 1789 | const composed = { prependPlugins: ['caf\u00e9-suite@market'] } |
| 1790 | const decomposed = { prependPlugins: ['cafe\u0301-suite@market'] } |
| 1791 | |
| 1792 | expect([ |
| 1793 | Hooks.isOrgTool(composed, 'mcp__caf_-suite__x'), |
| 1794 | Hooks.isOrgTool(composed, 'mcp__cafe_-suite__x'), |
| 1795 | Hooks.isOrgTool(decomposed, 'mcp__caf_-suite__x'), |
| 1796 | Hooks.isOrgTool(decomposed, 'mcp__cafe_-suite__x'), |
| 1797 | Hooks.isOrgTool(composed, 'mcp__plugin_caf_-suite_jira__x'), |
| 1798 | Hooks.namesOrgTool(composed, 'caf.-suite', 'x'), |
| 1799 | Hooks.namesOrgTool(composed, 'cafe\u0301-suite', 'x'), |
| 1800 | Hooks.namesOrgTool(decomposed, 'caf\u00e9-suite', 'x'), |
| 1801 | ]).toEqual([true, true, true, true, true, true, true, true]) |
| 1802 | }) |
| 1803 | |
| 1804 | test('a combining mark on a plain letter does not part a name', () => { |
| 1805 | const decomposed = { prependPlugins: ['cafe\u0301-suite@market'] } |
| 1806 | |
| 1807 | expect([ |
| 1808 | Hooks.namesOrgTool(Fixtures.SEATED_SUITE, 'corp-suite\u0301', 'x'), |
| 1809 | Hooks.isOrgTool( |
| 1810 | Fixtures.SEATED_SUITE, |
| 1811 | 'mcp__plugin_corp-suite\u0301_jira__x', |
| 1812 | ), |
| 1813 | Hooks.namesOrgTool(decomposed, 'cafe\u0301-suite', 'x'), |
| 1814 | Hooks.isOrgTool(decomposed, 'mcp__cafe\u0301-suite__x'), |
| 1815 | ]).toEqual([true, true, true, true]) |
| 1816 | }) |
| 1817 | |
| 1818 | test('a name in plain letters folds exactly as before', () => { |
| 1819 | expect( |
| 1820 | ['Corp__Tools', 'corp-tools', 'corp.tools', 'CORP TOOLS'].map( |
| 1821 | Hooks.toolPrefixOf, |
| 1822 | ), |
| 1823 | ).toEqual([ |
| 1824 | 'mcp_corp_tools_', |
| 1825 | 'mcp_corp_tools_', |
| 1826 | 'mcp_corp_tools_', |
| 1827 | 'mcp_corp_tools_', |
| 1828 | ]) |
| 1829 | }) |
| 1830 | |
| 1831 | test("a name unlike a seated plugin's servers is nobody else's", () => { |
| 1832 | expect([ |
| 1833 | Hooks.namesOrgTool(Fixtures.SEATED_SUITE, 'mine', 'greet'), |
| 1834 | Hooks.namesOrgTool(Fixtures.SEATED_SUITE, 'plugin_mine_jira', 'x'), |
| 1835 | Hooks.namesOrgTool(Fixtures.SEATED_SUITE, 'plugin_corp-suites', 'x'), |
| 1836 | Hooks.isOrgTool(Fixtures.SEATED_SUITE, 'mcp__plugin_mine_jira__x'), |
| 1837 | Hooks.isOrgTool(Fixtures.SEATED_SUITE, 'mcp__plugin_corp-suites_j__x'), |
| 1838 | Hooks.isOrgTool(Fixtures.SEATED_SUITE, 'mcp__mine__greet'), |
| 1839 | ]).toEqual([false, false, false, false, false, false]) |
| 1840 | }) |
| 1841 | |
| 1842 | test('a server the allowlist names counts as one it delivers does', () => { |
| 1843 | expect( |
| 1844 | [Fixtures.ALLOWLIST, Fixtures.NO_ALLOWLIST, {}].map(policy => |
| 1845 | Hooks.namesOrgTool(policy, 'corp__x', 'greet'), |
| 1846 | ), |
| 1847 | ).toEqual([true, false, false]) |
| 1848 | }) |
| 1849 | |
| 1850 | test('a server named with a dot or a space owns its tools', () => { |
| 1851 | expect( |
| 1852 | ['mcp__corp_tools__x', 'mcp__my_wiki__x', 'mcp__corp__x'].map(tool => |
| 1853 | Hooks.isOrgTool( |
| 1854 | { managedMcpServers: { 'corp.tools': {}, 'my wiki': {} } }, |
| 1855 | tool, |
| 1856 | ), |
| 1857 | ), |
| 1858 | ).toEqual([true, true, false]) |
| 1859 | }) |
| 1860 | |
| 1861 | test("an organization's ask cap holds over a plugin's allow", () => { |
| 1862 | expect( |
| 1863 | Hooks.holdsOver(Fixtures.CEILING_ASK, Fixtures.CEILING_ALLOW, undefined), |
| 1864 | ).toBe(true) |
| 1865 | }) |
| 1866 | |
| 1867 | test('the launcher variable is pinned where policy names a launcher', () => { |
| 1868 | expect( |
| 1869 | [ |
| 1870 | 'CLAUDE_CODE_PROCESS_WRAPPER', |
| 1871 | 'claude_code_process_wrapper', |
| 1872 | 'CORP_PROXY', |
| 1873 | ].map(name => Hooks.isPinnedVariable(Fixtures.WRAPPING_POLICY, name)), |
| 1874 | ).toEqual([true, true, false]) |
| 1875 | }) |
| 1876 | |
| 1877 | test('a server whose name folds to nothing refuses every name', () => { |
| 1878 | expect( |
| 1879 | ['mine', 'corp', 'x__y'].map(plugin => |
| 1880 | Hooks.namesOrgTool(Fixtures.UNFOLDABLE_POLICY, plugin, 'greet'), |
| 1881 | ), |
| 1882 | ).toEqual([true, true, true]) |
| 1883 | }) |
| 1884 | |
| 1885 | test("a server's tools are told by its name whole, as the engine spells it", () => { |
| 1886 | expect( |
| 1887 | ['mcp__corp__x', 'mcp__corp-tools__x', 'mcp__Corp__x', 'mcp__--__x'].map( |
| 1888 | tool => |
| 1889 | Hooks.isOrgTool({ managedMcpServers: { corp: {}, '--': {} } }, tool), |
| 1890 | ), |
| 1891 | ).toEqual([true, false, false, true]) |
| 1892 | }) |
| 1893 | |
| 1894 | test("a server named corp does not claim a corp-tools server's tools", () => { |
| 1895 | expect( |
| 1896 | Hooks.isOrgTool( |
| 1897 | { managedMcpServers: { corp: {} } }, |
| 1898 | 'mcp__corp-tools__x', |
| 1899 | ), |
| 1900 | ).toBe(false) |
| 1901 | }) |
| 1902 | |
| 1903 | test( |
| 1904 | 'a compaction is not theirs to answer', |
| 1905 | { plugins: [Fixtures.compacting] }, |
| 1906 | async ($, on) => { |
| 1907 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 1908 | on('session.compact', ($, e) => ({ messages: e.messages })) |
| 1909 | |
| 1910 | expect( |
| 1911 | await $.session.compact({ |
| 1912 | trigger: 'plugin', |
| 1913 | messages: [Fixtures.COMPACTED_TURN], |
| 1914 | }), |
| 1915 | ).toEqual({ messages: [Fixtures.COMPACTED_TURN] }) |
| 1916 | }, |
| 1917 | ) |
| 1918 | |
| 1919 | test("a tool whose own name folds to nothing reads as the server's too", () => { |
| 1920 | expect( |
| 1921 | [ |
| 1922 | ['Corp', '-'], |
| 1923 | ['Corp', '_'], |
| 1924 | ['other', '-'], |
| 1925 | ].map(([plugin = '', tool = '']) => |
| 1926 | Hooks.namesOrgTool({ managedMcpServers: { corp: {} } }, plugin, tool), |
| 1927 | ), |
| 1928 | ).toEqual([true, true, false]) |
| 1929 | }) |
| 1930 | |
| 1931 | test("an organization's own plugins' names are refused like its servers'", () => { |
| 1932 | const policy = { |
| 1933 | prependPlugins: ['corp-guard@market'], |
| 1934 | appendPlugins: ['corp-audit@market', 'plain'], |
| 1935 | } |
| 1936 | |
| 1937 | expect( |
| 1938 | [ |
| 1939 | ['Corp_Audit', 'x'], |
| 1940 | ['corp.guard', 'x'], |
| 1941 | ['PLAIN', 'x'], |
| 1942 | ['mine', 'x'], |
| 1943 | ].map(([plugin = '', tool = '']) => |
| 1944 | Hooks.namesOrgTool(policy, plugin, tool), |
| 1945 | ), |
| 1946 | ).toEqual([true, true, true, false]) |
| 1947 | expect( |
| 1948 | ['mcp__corp-audit__x', 'mcp__plain__x', 'mcp__mine__x'].map(tool => |
| 1949 | Hooks.isOrgTool(policy, tool), |
| 1950 | ), |
| 1951 | ).toEqual([true, true, false]) |
| 1952 | }) |
| 1953 | |
| 1954 | test('an unreadable server file refuses every name and owns every server tool', () => { |
| 1955 | const policy = { managedMcpServersUnreadable: true } |
| 1956 | |
| 1957 | expect(Hooks.namesOrgTool(policy, 'mine', 'greet')).toBe(true) |
| 1958 | expect( |
| 1959 | ['mcp__any__x', 'Bash'].map(tool => Hooks.isOrgTool(policy, tool)), |
| 1960 | ).toEqual([true, false]) |
| 1961 | expect(Hooks.isPinnedVariable(policy, 'CORP_PROXY')).toBe(false) |
| 1962 | }) |
| 1963 | |
| 1964 | test( |
| 1965 | "a compaction's summary is kept whole over the plugins they installed", |
| 1966 | { plugins: [Fixtures.blankingRows] }, |
| 1967 | async ($, on) => { |
| 1968 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 1969 | |
| 1970 | expect(await $.session.append(Fixtures.COMPACTION_ROW)).toMatchObject({ |
| 1971 | message: { content: Fixtures.COMPACTION_ROW.message.content }, |
| 1972 | }) |
| 1973 | }, |
| 1974 | ) |
| 1975 | |
| 1976 | test("a plugin managed settings turn on is the organization's by name too", () => { |
| 1977 | const policy = { |
| 1978 | enabledPlugins: { |
| 1979 | 'corp-guard@corp-market': true, |
| 1980 | 'left-off@corp-market': false, |
| 1981 | 'diff@builtin': true, |
| 1982 | }, |
| 1983 | } |
| 1984 | |
| 1985 | expect( |
| 1986 | ['Corp_Guard', 'left-off', 'diff', 'mine'].map(plugin => |
| 1987 | Hooks.namesOrgTool(policy, plugin, 'x'), |
| 1988 | ), |
| 1989 | ).toEqual([true, false, false, false]) |
| 1990 | expect( |
| 1991 | ['mcp__corp-guard__x', 'mcp__left-off__x'].map(tool => |
| 1992 | Hooks.isOrgTool(policy, tool), |
| 1993 | ), |
| 1994 | ).toEqual([true, false]) |
| 1995 | }) |
| 1996 | |
| 1997 | test( |
| 1998 | "a skill the Skill tool hands over is kept whole; another tool's row is theirs", |
| 1999 | { plugins: [Fixtures.blankingRows] }, |
| 2000 | async ($, on) => { |
| 2001 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 2002 | |
| 2003 | expect(await $.session.append(Fixtures.SKILL_ROW)).toMatchObject({ |
| 2004 | message: { content: Fixtures.SKILL_ROW.message.content }, |
| 2005 | }) |
| 2006 | expect( |
| 2007 | await $.session.append(Fixtures.READ_MESSAGE_ROW), |
| 2008 | ).not.toMatchObject({ |
| 2009 | message: { content: Fixtures.READ_MESSAGE_ROW.message.content }, |
| 2010 | }) |
| 2011 | }, |
| 2012 | ) |
| 2013 | |
| 2014 | test( |
| 2015 | "the organization's own plugin in the user tier keeps its name", |
| 2016 | { |
| 2017 | plugins: [ |
| 2018 | Fixtures.registering('corp-guard'), |
| 2019 | Fixtures.registering('mine'), |
| 2020 | ], |
| 2021 | }, |
| 2022 | async ($, on) => { |
| 2023 | on('settings.read', () => ({ |
| 2024 | value: { enabledPlugins: { 'corp-guard@claude-plugin-test': true } }, |
| 2025 | })) |
| 2026 | |
| 2027 | const registered = Fixtures.toolsRegistered(on) |
| 2028 | |
| 2029 | await $.session.start(Fixtures.SESSION) |
| 2030 | |
| 2031 | expect(registered).toEqual(['corp-guard', 'mine']) |
| 2032 | }, |
| 2033 | ) |
| 2034 | |
| 2035 | test( |
| 2036 | 'and keeps it when a version list turns it on', |
| 2037 | { |
| 2038 | plugins: [ |
| 2039 | Fixtures.registering('corp-guard'), |
| 2040 | Fixtures.registering('mine'), |
| 2041 | ], |
| 2042 | }, |
| 2043 | async ($, on) => { |
| 2044 | on('settings.read', () => ({ |
| 2045 | value: { |
| 2046 | enabledPlugins: { 'corp-guard@claude-plugin-test': ['^1.0.0'] }, |
| 2047 | }, |
| 2048 | })) |
| 2049 | |
| 2050 | const registered = Fixtures.toolsRegistered(on) |
| 2051 | |
| 2052 | await $.session.start(Fixtures.SESSION) |
| 2053 | |
| 2054 | expect(registered).toEqual(['corp-guard', 'mine']) |
| 2055 | }, |
| 2056 | ) |
| 2057 | |
| 2058 | test( |
| 2059 | 'an id in another letter case is the same id, as the engine folds one', |
| 2060 | { |
| 2061 | plugins: [ |
| 2062 | Fixtures.registering('corp-guard'), |
| 2063 | Fixtures.registering('mine'), |
| 2064 | ], |
| 2065 | }, |
| 2066 | async ($, on) => { |
| 2067 | on('settings.read', () => ({ |
| 2068 | value: { enabledPlugins: { 'Corp-Guard@Claude-Plugin-Test': true } }, |
| 2069 | })) |
| 2070 | |
| 2071 | const registered = Fixtures.toolsRegistered(on) |
| 2072 | |
| 2073 | await $.session.start(Fixtures.SESSION) |
| 2074 | |
| 2075 | expect(registered).toEqual(['corp-guard', 'mine']) |
| 2076 | }, |
| 2077 | ) |
| 2078 | |
| 2079 | test( |
| 2080 | 'an id listed only for its place exempts nobody', |
| 2081 | { |
| 2082 | plugins: [ |
| 2083 | Fixtures.registering('corp-guard'), |
| 2084 | Fixtures.registering('mine'), |
| 2085 | ], |
| 2086 | }, |
| 2087 | async ($, on) => { |
| 2088 | on('settings.read', () => ({ |
| 2089 | value: { prependPlugins: ['corp-guard@claude-plugin-test'] }, |
| 2090 | })) |
| 2091 | |
| 2092 | const registered = Fixtures.toolsRegistered(on) |
| 2093 | |
| 2094 | await $.session.start(Fixtures.SESSION) |
| 2095 | |
| 2096 | expect(registered).toEqual(['mine']) |
| 2097 | }, |
| 2098 | ) |
| 2099 | |
| 2100 | test( |
| 2101 | 'an id managed settings turn off exempts nobody', |
| 2102 | { |
| 2103 | plugins: [ |
| 2104 | Fixtures.registering('corp-guard'), |
| 2105 | Fixtures.registering('mine'), |
| 2106 | ], |
| 2107 | }, |
| 2108 | async ($, on) => { |
| 2109 | on('settings.read', () => ({ |
| 2110 | value: { |
| 2111 | enabledPlugins: { 'corp-guard@claude-plugin-test': false }, |
| 2112 | appendPlugins: ['corp-guard@claude-plugin-test'], |
| 2113 | }, |
| 2114 | })) |
| 2115 | |
| 2116 | const registered = Fixtures.toolsRegistered(on) |
| 2117 | |
| 2118 | await $.session.start(Fixtures.SESSION) |
| 2119 | |
| 2120 | expect(registered).toEqual(['mine']) |
| 2121 | }, |
| 2122 | ) |
| 2123 | |
| 2124 | test( |
| 2125 | "a plugin whose name only folds to the organization's is refused", |
| 2126 | { |
| 2127 | plugins: [ |
| 2128 | Fixtures.registering('corp--guard'), |
| 2129 | Fixtures.registering('mine'), |
| 2130 | ], |
| 2131 | }, |
| 2132 | async ($, on) => { |
| 2133 | on('settings.read', () => ({ |
| 2134 | value: { enabledPlugins: { 'corp-guard@claude-plugin-test': true } }, |
| 2135 | })) |
| 2136 | |
| 2137 | const registered = Fixtures.toolsRegistered(on) |
| 2138 | |
| 2139 | await $.session.start(Fixtures.SESSION) |
| 2140 | |
| 2141 | expect(registered).toEqual(['mine']) |
| 2142 | }, |
| 2143 | ) |
| 2144 | |
| 2145 | test( |
| 2146 | 'the plugin the engine seats as the organization keeps its own name', |
| 2147 | { plugins: [Fixtures.registering('corp-guard', 'prepend')] }, |
| 2148 | async ($, on) => { |
| 2149 | on('settings.read', () => ({ |
| 2150 | value: { enabledPlugins: { 'corp-guard@claude-plugin-test': true } }, |
| 2151 | })) |
| 2152 | |
| 2153 | const registered = Fixtures.toolsRegistered(on) |
| 2154 | |
| 2155 | await $.session.start(Fixtures.SESSION) |
| 2156 | |
| 2157 | expect(registered).toEqual(['corp-guard']) |
| 2158 | }, |
| 2159 | ) |
| 2160 | |
| 2161 | test( |
| 2162 | 'a plugin of the same name from another source is refused it', |
| 2163 | { |
| 2164 | plugins: [ |
| 2165 | Fixtures.registering('corp-guard'), |
| 2166 | Fixtures.registering('mine'), |
| 2167 | ], |
| 2168 | }, |
| 2169 | async ($, on) => { |
| 2170 | on('settings.read', () => ({ |
| 2171 | value: { enabledPlugins: { 'corp-guard@corp-market': true } }, |
| 2172 | })) |
| 2173 | |
| 2174 | const registered = Fixtures.toolsRegistered(on) |
| 2175 | |
| 2176 | await $.session.start(Fixtures.SESSION) |
| 2177 | |
| 2178 | expect(registered).toEqual(['mine']) |
| 2179 | }, |
| 2180 | ) |
| 2181 | |
| 2182 | test( |
| 2183 | 'the folder managed settings are read from is not theirs to repoint', |
| 2184 | { plugins: [Fixtures.repointingSettings('mine')] }, |
| 2185 | async ($, on) => { |
| 2186 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 2187 | Fixtures.logged(on) |
| 2188 | |
| 2189 | const set = Fixtures.variablesSet(on) |
| 2190 | |
| 2191 | await $.session.start(Fixtures.SESSION) |
| 2192 | |
| 2193 | expect(set).toEqual([]) |
| 2194 | }, |
| 2195 | ) |
| 2196 | |
| 2197 | test('that variable is pinned whatever managed settings hold, in any case', () => { |
| 2198 | expect( |
| 2199 | [ |
| 2200 | 'CLAUDE_CODE_MANAGED_SETTINGS_PATH', |
| 2201 | 'claude_code_managed_settings_path', |
| 2202 | 'CORP_PROXY', |
| 2203 | ].map(name => Hooks.isPinnedVariable({}, name)), |
| 2204 | ).toEqual([true, true, false]) |
| 2205 | }) |
| 2206 | |
| 2207 | test('a scoped id is parted at its last at-sign', () => { |
| 2208 | const policy = { prependPlugins: ['@scope/corp-guard@npm'] } |
| 2209 | |
| 2210 | expect( |
| 2211 | ['scope-corp-guard', 'Scope.Corp.Guard', 'mine'].map(plugin => |
| 2212 | Hooks.namesOrgTool(policy, plugin, 'x'), |
| 2213 | ), |
| 2214 | ).toEqual([true, true, false]) |
| 2215 | expect(Hooks.isOrgTool(policy, 'mcp__scope_corp-guard__x')).toBe(true) |
| 2216 | }) |
| 2217 | |
| 2218 | test('the override switch in a policy the person can write lifts nothing', () => { |
| 2219 | const written = { |
| 2220 | pluginConfigs: { |
| 2221 | 'cc-plugin-sec-default@builtin': { |
| 2222 | options: { allowModsToOverrideDenyRules: true }, |
| 2223 | }, |
| 2224 | }, |
| 2225 | } |
| 2226 | |
| 2227 | expect( |
| 2228 | [written, { ...written, personWritablePolicy: true }].map(policy => |
| 2229 | Hooks.denyRulesHold(policy), |
| 2230 | ), |
| 2231 | ).toEqual([false, true]) |
| 2232 | }) |
| 2233 | |
| 2234 | test('every variable that says where policy is read from is pinned', () => { |
| 2235 | expect( |
| 2236 | [ |
| 2237 | 'CLAUDE_CODE_MANAGED_SETTINGS_PATH', |
| 2238 | 'CLAUDE_CODE_REMOTE_SETTINGS_PATH', |
| 2239 | 'CLAUDE_CODE_DISABLE_ADMIN_ENV_UNION', |
| 2240 | 'CLAUDE_CODE_MOCK_REMOTE_SETTINGS', |
| 2241 | 'CLAUDE_CONFIG_DIR', |
| 2242 | 'CLAUDE_SECURESTORAGE_CONFIG_DIR', |
| 2243 | 'USE_LOCAL_OAUTH', |
| 2244 | 'USE_STAGING_OAUTH', |
| 2245 | 'CLAUDE_LOCAL_OAUTH_API_BASE', |
| 2246 | ].map(name => Hooks.isPinnedVariable({}, name)), |
| 2247 | ).toEqual([true, true, true, true, true, true, true, true, true]) |
| 2248 | }) |
| 2249 | |
| 2250 | test('every variable that can load code into a child is pinned', () => { |
| 2251 | expect( |
| 2252 | [ |
| 2253 | 'LD_PRELOAD', |
| 2254 | 'LD_LIBRARY_PATH', |
| 2255 | 'LD_AUDIT', |
| 2256 | 'DYLD_INSERT_LIBRARIES', |
| 2257 | 'DYLD_LIBRARY_PATH', |
| 2258 | 'DYLD_FRAMEWORK_PATH', |
| 2259 | 'BASH_ENV', |
| 2260 | 'ENV', |
| 2261 | 'ZDOTDIR', |
| 2262 | 'NODE_OPTIONS', |
| 2263 | 'ld_preload', |
| 2264 | 'PATH', |
| 2265 | 'HOME', |
| 2266 | ].map(name => Hooks.isPinnedVariable({}, name)), |
| 2267 | ).toEqual([ |
| 2268 | true, |
| 2269 | true, |
| 2270 | true, |
| 2271 | true, |
| 2272 | true, |
| 2273 | true, |
| 2274 | true, |
| 2275 | true, |
| 2276 | true, |
| 2277 | true, |
| 2278 | true, |
| 2279 | false, |
| 2280 | false, |
| 2281 | ]) |
| 2282 | }) |
| 2283 | |
| 2284 | test( |
| 2285 | "LD_PRELOAD is not a person's plugin's to set", |
| 2286 | { |
| 2287 | plugins: [ |
| 2288 | Fixtures.settingLdPreload('suite', 'prepend'), |
| 2289 | Fixtures.settingLdPreload('mine'), |
| 2290 | Fixtures.settingLdPreload('bundled', 'builtin'), |
| 2291 | ], |
| 2292 | }, |
| 2293 | async ($, on) => { |
| 2294 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 2295 | |
| 2296 | const lines = Fixtures.logged(on) |
| 2297 | const set = Fixtures.variablesSet(on) |
| 2298 | |
| 2299 | await $.session.start(Fixtures.SESSION) |
| 2300 | |
| 2301 | expect(set).toEqual([ |
| 2302 | 'suite LD_PRELOAD=mine', |
| 2303 | 'bundled LD_PRELOAD=mine', |
| 2304 | ]) |
| 2305 | expect(lines).toHaveLength(1) |
| 2306 | expect(lines[0]).toContain(Hooks.pinnedVariableRefusal('LD_PRELOAD')) |
| 2307 | }, |
| 2308 | ) |
| 2309 | |
| 2310 | test( |
| 2311 | "DYLD_INSERT_LIBRARIES is not a person's plugin's to set", |
| 2312 | { |
| 2313 | plugins: [ |
| 2314 | Fixtures.settingDyldInsert('suite', 'prepend'), |
| 2315 | Fixtures.settingDyldInsert('mine'), |
| 2316 | Fixtures.settingDyldInsert('bundled', 'builtin'), |
| 2317 | ], |
| 2318 | }, |
| 2319 | async ($, on) => { |
| 2320 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 2321 | |
| 2322 | const lines = Fixtures.logged(on) |
| 2323 | const set = Fixtures.variablesSet(on) |
| 2324 | |
| 2325 | await $.session.start(Fixtures.SESSION) |
| 2326 | |
| 2327 | expect(set).toEqual([ |
| 2328 | 'suite DYLD_INSERT_LIBRARIES=mine', |
| 2329 | 'bundled DYLD_INSERT_LIBRARIES=mine', |
| 2330 | ]) |
| 2331 | expect(lines).toHaveLength(1) |
| 2332 | expect(lines[0]).toContain( |
| 2333 | Hooks.pinnedVariableRefusal('DYLD_INSERT_LIBRARIES'), |
| 2334 | ) |
| 2335 | }, |
| 2336 | ) |
| 2337 | |
| 2338 | test( |
| 2339 | "BASH_ENV is not a person's plugin's to set", |
| 2340 | { |
| 2341 | plugins: [ |
| 2342 | Fixtures.settingBashEnv('suite', 'prepend'), |
| 2343 | Fixtures.settingBashEnv('mine'), |
| 2344 | Fixtures.settingBashEnv('bundled', 'builtin'), |
| 2345 | ], |
| 2346 | }, |
| 2347 | async ($, on) => { |
| 2348 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 2349 | |
| 2350 | const lines = Fixtures.logged(on) |
| 2351 | const set = Fixtures.variablesSet(on) |
| 2352 | |
| 2353 | await $.session.start(Fixtures.SESSION) |
| 2354 | |
| 2355 | expect(set).toEqual([ |
| 2356 | 'suite BASH_ENV=mine', |
| 2357 | 'bundled BASH_ENV=mine', |
| 2358 | ]) |
| 2359 | expect(lines).toHaveLength(1) |
| 2360 | expect(lines[0]).toContain(Hooks.pinnedVariableRefusal('BASH_ENV')) |
| 2361 | }, |
| 2362 | ) |
| 2363 | |
| 2364 | test( |
| 2365 | "NODE_OPTIONS is not a person's plugin's to set", |
| 2366 | { |
| 2367 | plugins: [ |
| 2368 | Fixtures.settingNodeOptions('suite', 'prepend'), |
| 2369 | Fixtures.settingNodeOptions('mine'), |
| 2370 | Fixtures.settingNodeOptions('bundled', 'builtin'), |
| 2371 | ], |
| 2372 | }, |
| 2373 | async ($, on) => { |
| 2374 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 2375 | |
| 2376 | const lines = Fixtures.logged(on) |
| 2377 | const set = Fixtures.variablesSet(on) |
| 2378 | |
| 2379 | await $.session.start(Fixtures.SESSION) |
| 2380 | |
| 2381 | expect(set).toEqual([ |
| 2382 | 'suite NODE_OPTIONS=mine', |
| 2383 | 'bundled NODE_OPTIONS=mine', |
| 2384 | ]) |
| 2385 | expect(lines).toHaveLength(1) |
| 2386 | expect(lines[0]).toContain(Hooks.pinnedVariableRefusal('NODE_OPTIONS')) |
| 2387 | }, |
| 2388 | ) |
| 2389 | |
| 2390 | test( |
| 2391 | 'nor is NODE_OPTIONS theirs to unset', |
| 2392 | { |
| 2393 | plugins: [ |
| 2394 | Fixtures.unsettingNodeOptions('suite', 'prepend'), |
| 2395 | Fixtures.unsettingNodeOptions('mine'), |
| 2396 | ], |
| 2397 | }, |
| 2398 | async ($, on) => { |
| 2399 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 2400 | |
| 2401 | const lines = Fixtures.logged(on) |
| 2402 | const set = Fixtures.variablesSet(on) |
| 2403 | |
| 2404 | await $.session.start(Fixtures.SESSION) |
| 2405 | |
| 2406 | expect(set).toEqual(['suite NODE_OPTIONS=undefined']) |
| 2407 | expect(lines).toHaveLength(1) |
| 2408 | expect(lines[0]).toContain(Hooks.pinnedVariableRefusal('NODE_OPTIONS')) |
| 2409 | }, |
| 2410 | ) |
| 2411 | |
| 2412 | test( |
| 2413 | 'a listed id that is one at-sign refuses every registration of theirs', |
| 2414 | { |
| 2415 | plugins: [Fixtures.registering('mine'), Fixtures.registering('other')], |
| 2416 | }, |
| 2417 | async ($, on) => { |
| 2418 | on('settings.read', () => ({ value: { prependPlugins: ['@'] } })) |
| 2419 | |
| 2420 | const registered = Fixtures.toolsRegistered(on) |
| 2421 | |
| 2422 | await $.session.start(Fixtures.SESSION) |
| 2423 | |
| 2424 | expect(registered).toEqual([]) |
| 2425 | }, |
| 2426 | ) |
| 2427 | |
| 2428 | test('a listed id with no name half protects what follows its at-sign', () => { |
| 2429 | const policy = { prependPlugins: ['@market'] } |
| 2430 | |
| 2431 | expect( |
| 2432 | ['market', 'Market', 'mine'].map(plugin => |
| 2433 | Hooks.namesOrgTool(policy, plugin, 'x'), |
| 2434 | ), |
| 2435 | ).toEqual([true, true, false]) |
| 2436 | }) |
| 2437 | }) |