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 | '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 | "the system prompt's sections pass over the plugins the person installed", |
| 160 | { plugins: [Fixtures.emptying, Fixtures.heading] }, |
| 161 | async ($, on) => { |
| 162 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 163 | |
| 164 | on('prompt.compose', () => ({ |
| 165 | sections: [{ id: 'body', text: 'the body', scope: 'shared' }], |
| 166 | })) |
| 167 | |
| 168 | expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({ |
| 169 | sections: [ |
| 170 | { id: 'heading:org', text: 'the org says hi', scope: 'shared' }, |
| 171 | { id: 'body', text: 'the body', scope: 'shared' }, |
| 172 | ], |
| 173 | }) |
| 174 | }, |
| 175 | ) |
| 176 | |
| 177 | test( |
| 178 | "nor does a person's plugin drop or reword a section it asked for", |
| 179 | { plugins: [Fixtures.rewording, Fixtures.heading] }, |
| 180 | async ($, on) => { |
| 181 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 182 | |
| 183 | on('prompt.compose', () => ({ |
| 184 | sections: [{ id: 'body', text: 'the body', scope: 'shared' }], |
| 185 | })) |
| 186 | |
| 187 | expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({ |
| 188 | sections: [ |
| 189 | { id: 'heading:org', text: 'the org says hi', scope: 'shared' }, |
| 190 | { id: 'body', text: 'the body', scope: 'shared' }, |
| 191 | ], |
| 192 | }) |
| 193 | }, |
| 194 | ) |
| 195 | |
| 196 | test( |
| 197 | "a user plugin's rewrite of policy is skipped for every other reader", |
| 198 | { |
| 199 | plugins: [ |
| 200 | Fixtures.stripping, |
| 201 | Fixtures.reading, |
| 202 | Fixtures.registering('mine'), |
| 203 | ], |
| 204 | }, |
| 205 | async ($, on) => { |
| 206 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 207 | |
| 208 | const registered = Fixtures.toolsRegistered(on) |
| 209 | const { text } = await $.command.run(Fixtures.POLICY_COMMAND) |
| 210 | |
| 211 | await $.session.start(Fixtures.SESSION) |
| 212 | |
| 213 | expect( |
| 214 | JSON.parse(text ?? 'null'), |
| 215 | "another user plugin's read sees the allowlist the stripper hides", |
| 216 | ).toEqual(Fixtures.MANAGED_POLICY) |
| 217 | |
| 218 | expect( |
| 219 | registered, |
| 220 | "sec-default's own tool.register hook still reads the allowlist", |
| 221 | ).toEqual([]) |
| 222 | }, |
| 223 | ) |
| 224 | |
| 225 | test( |
| 226 | "an organization provider's subject passes over the user plugins", |
| 227 | { plugins: [Fixtures.marking] }, |
| 228 | async ($, on) => { |
| 229 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 230 | Fixtures.subjectsEchoed(on) |
| 231 | |
| 232 | for (const provider of Fixtures.ORG_PROVIDERS) { |
| 233 | expect( |
| 234 | await $.tool.describe( |
| 235 | Fixtures.toolDescribed('mcp__corp__search', provider), |
| 236 | ), |
| 237 | ).toEqual({ description: 'd' }) |
| 238 | |
| 239 | expect( |
| 240 | ( |
| 241 | await $.command.describe( |
| 242 | Fixtures.commandDescribed('suite:deploy', provider), |
| 243 | ) |
| 244 | ).isHidden, |
| 245 | ).toBe(false) |
| 246 | |
| 247 | expect( |
| 248 | await $.agent.offer( |
| 249 | Fixtures.agentOffered('suite:reviewer', provider), |
| 250 | ), |
| 251 | ).toEqual({ isOffered: true }) |
| 252 | |
| 253 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 254 | model: 'core', |
| 255 | }) |
| 256 | } |
| 257 | |
| 258 | for (const provider of Fixtures.USER_REACHABLE_PROVIDERS) { |
| 259 | expect( |
| 260 | await $.tool.describe( |
| 261 | Fixtures.toolDescribed('mcp__mine__search', provider), |
| 262 | ), |
| 263 | ).toEqual({ description: 'user: d' }) |
| 264 | |
| 265 | expect( |
| 266 | ( |
| 267 | await $.command.describe( |
| 268 | Fixtures.commandDescribed('mine:deploy', provider), |
| 269 | ) |
| 270 | ).isHidden, |
| 271 | ).toBe(true) |
| 272 | |
| 273 | expect( |
| 274 | await $.agent.offer(Fixtures.agentOffered('reviewer', provider)), |
| 275 | ).toEqual({ isOffered: false }) |
| 276 | |
| 277 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 278 | model: 'user', |
| 279 | }) |
| 280 | } |
| 281 | }, |
| 282 | ) |
| 283 | |
| 284 | test( |
| 285 | 'an odd provider passes over the user plugins: it fails closed', |
| 286 | { plugins: [Fixtures.marking] }, |
| 287 | async ($, on) => { |
| 288 | on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST })) |
| 289 | Fixtures.subjectsEchoed(on) |
| 290 | |
| 291 | for (const provider of Fixtures.ODD_PROVIDERS) { |
| 292 | expect( |
| 293 | await $.tool.describe(Fixtures.toolDescribed('Bash', provider)), |
| 294 | ).toEqual({ description: 'd' }) |
| 295 | |
| 296 | expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ |
| 297 | model: 'core', |
| 298 | }) |
| 299 | } |
| 300 | }, |
| 301 | ) |
| 302 | |
| 303 | test( |
| 304 | 'a subject decision reads no policy; a burst of listings reads once', |
| 305 | { plugins: [Fixtures.listing] }, |
| 306 | async ($, on) => { |
| 307 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 308 | |
| 309 | Fixtures.subjectsEchoed(on) |
| 310 | on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) |
| 311 | |
| 312 | for (const provider of Fixtures.ORG_PROVIDERS) { |
| 313 | await $.tool.describe(Fixtures.toolDescribed('mcp__corp__x', provider)) |
| 314 | await $.agent.spawn(Fixtures.agentSpawned(provider)) |
| 315 | } |
| 316 | |
| 317 | expect(reads(), "one read: the person's plugin was admitted").toBe(1) |
| 318 | |
| 319 | await Promise.all([ |
| 320 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 321 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 322 | $.command.run(Fixtures.TOOLS_COMMAND), |
| 323 | ]) |
| 324 | |
| 325 | expect(reads()).toBe(2) |
| 326 | }, |
| 327 | ) |
| 328 | |
| 329 | test( |
| 330 | 'the refusal a user-tier caller reads names the allowlist', |
| 331 | { |
| 332 | plugins: [ |
| 333 | { |
| 334 | name: 'asking', |
| 335 | register(on) { |
| 336 | on('command.run', { command: 'greet' }, $ => |
| 337 | $.tool |
| 338 | .register({ |
| 339 | name: 'greet', |
| 340 | description: 'Says hello.', |
| 341 | inputSchema: { type: 'object' }, |
| 342 | }) |
| 343 | .then( |
| 344 | () => ({ text: 'registered' }), |
| 345 | (error: unknown) => ({ text: String(error) }), |
| 346 | ), |
| 347 | ) |
| 348 | }, |
| 349 | }, |
| 350 | ], |
| 351 | }, |
| 352 | async ($, on) => { |
| 353 | on('settings.read', () => ({ value: Fixtures.ALLOWLIST })) |
| 354 | |
| 355 | const { text } = await $.command.run({ |
| 356 | command: 'greet', |
| 357 | args: '', |
| 358 | origin: { kind: 'composer' }, |
| 359 | presentation: Fixtures.FULLSCREEN, |
| 360 | }) |
| 361 | |
| 362 | expect(text).toEndWith( |
| 363 | `asking: $.tool.register: ${Hooks.TOOL_REGISTER_REFUSAL}`, |
| 364 | ) |
| 365 | }, |
| 366 | ) |
| 367 | |
| 368 | test( |
| 369 | "under allowManagedModsOnly a person's mod is kept out, told why", |
| 370 | { plugins: [Fixtures.registering('mine')] }, |
| 371 | async ($, on) => { |
| 372 | on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY })) |
| 373 | |
| 374 | const registered = Fixtures.toolsRegistered(on) |
| 375 | |
| 376 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 377 | Hooks.managedModsOnlyRefusal('mine'), |
| 378 | ) |
| 379 | |
| 380 | expect(registered, 'no hook of it ever ran').toEqual([]) |
| 381 | }, |
| 382 | ) |
| 383 | |
| 384 | test( |
| 385 | "under it the organization's mods and the built-ins load", |
| 386 | { |
| 387 | plugins: [ |
| 388 | Fixtures.registering('suite', 'prepend'), |
| 389 | Fixtures.registering('after', 'append'), |
| 390 | Fixtures.registering('bundled', 'builtin'), |
| 391 | ], |
| 392 | }, |
| 393 | async ($, on) => { |
| 394 | on('settings.read', () => ({ value: Fixtures.MANAGED_MODS_ONLY })) |
| 395 | |
| 396 | const registered = Fixtures.toolsRegistered(on) |
| 397 | |
| 398 | await $.session.start(Fixtures.SESSION) |
| 399 | |
| 400 | expect(registered).toEqual(['suite', 'after', 'bundled']) |
| 401 | }, |
| 402 | ) |
| 403 | |
| 404 | test( |
| 405 | 'set to false, a mod the person installed loads as before', |
| 406 | { plugins: [Fixtures.registering('mine')] }, |
| 407 | async ($, on) => { |
| 408 | on('settings.read', () => ({ value: Fixtures.modsPolicyOf(false) })) |
| 409 | |
| 410 | const registered = Fixtures.toolsRegistered(on) |
| 411 | |
| 412 | await $.session.start(Fixtures.SESSION) |
| 413 | |
| 414 | expect(registered).toEqual(['mine']) |
| 415 | }, |
| 416 | ) |
| 417 | |
| 418 | test( |
| 419 | "a person's copy under an organization mod's name is still theirs", |
| 420 | { plugins: [Fixtures.registering('suite')] }, |
| 421 | async ($, on) => { |
| 422 | on('settings.read', () => ({ |
| 423 | value: { ...Fixtures.MANAGED_POLICY, ...Fixtures.MANAGED_MODS_ONLY }, |
| 424 | })) |
| 425 | |
| 426 | const registered = Fixtures.toolsRegistered(on) |
| 427 | |
| 428 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 429 | Hooks.managedModsOnlyRefusal('suite'), |
| 430 | ) |
| 431 | |
| 432 | expect(registered).toEqual([]) |
| 433 | }, |
| 434 | ) |
| 435 | |
| 436 | test( |
| 437 | "only managed settings are asked: a person's own cannot loosen it", |
| 438 | { plugins: [Fixtures.registering('mine')] }, |
| 439 | async ($, on) => { |
| 440 | Fixtures.policyBySource( |
| 441 | on, |
| 442 | Fixtures.MANAGED_MODS_ONLY, |
| 443 | Fixtures.modsPolicyOf(false), |
| 444 | ) |
| 445 | |
| 446 | const registered = Fixtures.toolsRegistered(on) |
| 447 | |
| 448 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 449 | Hooks.managedModsOnlyRefusal('mine'), |
| 450 | ) |
| 451 | |
| 452 | expect(registered).toEqual([]) |
| 453 | }, |
| 454 | ) |
| 455 | |
| 456 | test( |
| 457 | "nor turn it on: set only in a person's settings, it is not read", |
| 458 | { plugins: [Fixtures.registering('mine')] }, |
| 459 | async ($, on) => { |
| 460 | Fixtures.policyBySource( |
| 461 | on, |
| 462 | Fixtures.NO_ALLOWLIST, |
| 463 | Fixtures.MANAGED_MODS_ONLY, |
| 464 | ) |
| 465 | |
| 466 | const registered = Fixtures.toolsRegistered(on) |
| 467 | |
| 468 | await $.session.start(Fixtures.SESSION) |
| 469 | |
| 470 | expect(registered).toEqual(['mine']) |
| 471 | }, |
| 472 | ) |
| 473 | |
| 474 | test( |
| 475 | "a policy that cannot be read keeps a person's mod out: the hook's catch", |
| 476 | { plugins: [Fixtures.registering('mine')] }, |
| 477 | async ($, on) => { |
| 478 | on('settings.read', () => ({ deny: 'managed settings unreadable' })) |
| 479 | |
| 480 | const lines = Fixtures.logged(on) |
| 481 | const registered = Fixtures.toolsRegistered(on) |
| 482 | |
| 483 | await expect($.session.start(Fixtures.SESSION)).rejects.toThrow( |
| 484 | Hooks.managedModsOnlyRefusal('mine'), |
| 485 | ) |
| 486 | |
| 487 | expect({ registered, lines }).toEqual({ |
| 488 | registered: [], |
| 489 | lines: [ |
| 490 | 'debug: plugin.register hook failed judging mine (throw): ' + |
| 491 | 'sec-default: $.settings.read: managed settings unreadable', |
| 492 | ], |
| 493 | }) |
| 494 | }, |
| 495 | ) |
| 496 | |
| 497 | test( |
| 498 | 'a deny rule holds over an allow from a plugin the person installed', |
| 499 | { plugins: [Fixtures.allowing('easy')] }, |
| 500 | async ($, on) => { |
| 501 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 502 | |
| 503 | const lines = Fixtures.logged(on) |
| 504 | |
| 505 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 506 | |
| 507 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 508 | |
| 509 | expect(lines).toEqual([ |
| 510 | `transcript: ${Hooks.heldNotice('easy', 'Bash', 'Bash(echo *)')}`, |
| 511 | ]) |
| 512 | }, |
| 513 | ) |
| 514 | |
| 515 | test( |
| 516 | 'two plugins of the person chained are each named once in a session', |
| 517 | { |
| 518 | plugins: [Fixtures.allowing('first'), Fixtures.asking('second')], |
| 519 | }, |
| 520 | async ($, on) => { |
| 521 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 522 | |
| 523 | const lines = Fixtures.logged(on) |
| 524 | |
| 525 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 526 | |
| 527 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 528 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 529 | |
| 530 | expect(lines.toSorted()).toEqual([ |
| 531 | `transcript: ${Hooks.heldNotice('first', 'Bash', 'Bash(echo *)')}`, |
| 532 | `transcript: ${Hooks.heldNotice('second', 'Bash', 'Bash(echo *)')}`, |
| 533 | ]) |
| 534 | }, |
| 535 | ) |
| 536 | |
| 537 | test( |
| 538 | 'a deny rule turned into an ask by a plugin of the person holds too', |
| 539 | { plugins: [Fixtures.asking('easy')] }, |
| 540 | async ($, on) => { |
| 541 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 542 | Fixtures.logged(on) |
| 543 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 544 | |
| 545 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 546 | }, |
| 547 | ) |
| 548 | |
| 549 | test( |
| 550 | "an organization plugin's allow over a deny rule stands, above or below", |
| 551 | { |
| 552 | plugins: [ |
| 553 | Fixtures.allowing('suite', 'append'), |
| 554 | Fixtures.allowing('easy'), |
| 555 | ], |
| 556 | }, |
| 557 | async ($, on) => { |
| 558 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 559 | |
| 560 | const lines = Fixtures.logged(on) |
| 561 | |
| 562 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 563 | |
| 564 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 565 | expect(lines).toEqual([]) |
| 566 | }, |
| 567 | ) |
| 568 | |
| 569 | test( |
| 570 | "a prepended organization plugin's allow over a deny rule stands", |
| 571 | { plugins: [Fixtures.allowing('guard', 'prepend')] }, |
| 572 | async ($, on) => { |
| 573 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 574 | |
| 575 | const lines = Fixtures.logged(on) |
| 576 | |
| 577 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 578 | |
| 579 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 580 | expect(lines).toEqual([]) |
| 581 | }, |
| 582 | ) |
| 583 | |
| 584 | test( |
| 585 | "a built-in's allow over a deny rule stands", |
| 586 | { plugins: [Fixtures.allowing('bundled', 'builtin')] }, |
| 587 | async ($, on) => { |
| 588 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 589 | |
| 590 | const lines = Fixtures.logged(on) |
| 591 | |
| 592 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 593 | |
| 594 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 595 | expect(lines).toEqual([]) |
| 596 | }, |
| 597 | ) |
| 598 | |
| 599 | test( |
| 600 | 'an ask a plugin of the person allows, no deny rule behind it, stands', |
| 601 | { plugins: [Fixtures.allowing('easy')] }, |
| 602 | async ($, on) => { |
| 603 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 604 | |
| 605 | const lines = Fixtures.logged(on) |
| 606 | |
| 607 | Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 608 | |
| 609 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 610 | expect(lines).toEqual([]) |
| 611 | }, |
| 612 | ) |
| 613 | |
| 614 | test( |
| 615 | 'a deny no rule decided is still theirs to answer over', |
| 616 | { plugins: [Fixtures.allowing('easy')] }, |
| 617 | async ($, on) => { |
| 618 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 619 | |
| 620 | const lines = Fixtures.logged(on) |
| 621 | |
| 622 | Fixtures.checksAnswered(on, Fixtures.PLAIN_DENY) |
| 623 | |
| 624 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 625 | expect(lines).toEqual([]) |
| 626 | }, |
| 627 | ) |
| 628 | |
| 629 | test( |
| 630 | 'a plugin of the person that tightens is heard, with one evaluation', |
| 631 | { plugins: [Fixtures.tightening] }, |
| 632 | async ($, on) => { |
| 633 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 634 | const evaluations = Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 635 | |
| 636 | await $.tool.check(Fixtures.CHECKED) |
| 637 | |
| 638 | const loaded = { reads: reads(), evaluations: evaluations() } |
| 639 | |
| 640 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.PLAIN_DENY) |
| 641 | |
| 642 | expect( |
| 643 | { |
| 644 | reads: reads() - loaded.reads, |
| 645 | evaluations: evaluations() - loaded.evaluations, |
| 646 | }, |
| 647 | 'once its plugins have loaded, a check reads no policy', |
| 648 | ).toEqual({ reads: 0, evaluations: 1 }) |
| 649 | }, |
| 650 | ) |
| 651 | |
| 652 | test( |
| 653 | "a deny rule holds when an organization's plugin listens above theirs", |
| 654 | { |
| 655 | plugins: [ |
| 656 | Fixtures.listening('audit', 'prepend'), |
| 657 | Fixtures.allowing('easy'), |
| 658 | ], |
| 659 | }, |
| 660 | async ($, on) => { |
| 661 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 662 | |
| 663 | const lines = Fixtures.logged(on) |
| 664 | |
| 665 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 666 | |
| 667 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 668 | |
| 669 | expect( |
| 670 | lines.map(line => line.includes('easy')), |
| 671 | 'one line, naming the plugin of theirs, alone or in its batch', |
| 672 | ).toEqual([true]) |
| 673 | }, |
| 674 | ) |
| 675 | |
| 676 | test( |
| 677 | 'a plugin of the person that only listens costs no second evaluation', |
| 678 | { plugins: [Fixtures.listening('listening')] }, |
| 679 | async ($, on) => { |
| 680 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 681 | const evaluations = Fixtures.checksAnswered(on, Fixtures.ASKED) |
| 682 | |
| 683 | await $.tool.check(Fixtures.CHECKED) |
| 684 | |
| 685 | const loaded = { reads: reads(), evaluations: evaluations() } |
| 686 | |
| 687 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ASKED) |
| 688 | |
| 689 | expect( |
| 690 | { |
| 691 | reads: reads() - loaded.reads, |
| 692 | evaluations: evaluations() - loaded.evaluations, |
| 693 | }, |
| 694 | 'once its plugins have loaded, a check reads no policy', |
| 695 | ).toEqual({ reads: 0, evaluations: 1 }) |
| 696 | }, |
| 697 | ) |
| 698 | |
| 699 | test('with none of their plugins the verdict passes once', async ($, on) => { |
| 700 | const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) |
| 701 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 702 | |
| 703 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 704 | |
| 705 | expect({ reads: reads(), evaluations: evaluations() }).toEqual({ |
| 706 | reads: 0, |
| 707 | evaluations: 1, |
| 708 | }) |
| 709 | }) |
| 710 | |
| 711 | test( |
| 712 | 'an allow that never called next meets the deny rule all the same', |
| 713 | { plugins: [Fixtures.blindAllowing] }, |
| 714 | async ($, on) => { |
| 715 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 716 | |
| 717 | const lines = Fixtures.logged(on) |
| 718 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 719 | |
| 720 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 721 | |
| 722 | expect({ lines, evaluations: evaluations() }).toEqual({ |
| 723 | lines: [ |
| 724 | `transcript: ${Hooks.heldNotice('blind', 'Bash', 'Bash(echo *)')}`, |
| 725 | ], |
| 726 | evaluations: 1, |
| 727 | }) |
| 728 | }, |
| 729 | ) |
| 730 | |
| 731 | test( |
| 732 | 'a rule a plugin of the person writes into its answer is never read', |
| 733 | { plugins: [Fixtures.forging] }, |
| 734 | async ($, on) => { |
| 735 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 736 | |
| 737 | const lines = Fixtures.logged(on) |
| 738 | |
| 739 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 740 | |
| 741 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 742 | |
| 743 | expect(lines).toEqual([ |
| 744 | `transcript: ${Hooks.heldNotice('forging', 'Bash', 'Bash(echo *)')}`, |
| 745 | ]) |
| 746 | }, |
| 747 | ) |
| 748 | |
| 749 | test( |
| 750 | 'a plugin of the person asking about another command is left out', |
| 751 | { plugins: [Fixtures.rewriting] }, |
| 752 | async ($, on) => { |
| 753 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 754 | Fixtures.logged(on) |
| 755 | |
| 756 | const asked: unknown[] = [] |
| 757 | |
| 758 | on('tool.check', ($, e) => { |
| 759 | asked.push(e.input) |
| 760 | |
| 761 | return Fixtures.RULE_DENY |
| 762 | }) |
| 763 | |
| 764 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 765 | |
| 766 | expect( |
| 767 | asked, |
| 768 | 'the rules were asked about the command that would run, only', |
| 769 | ).toEqual([Fixtures.CHECKED.input]) |
| 770 | }, |
| 771 | ) |
| 772 | |
| 773 | test( |
| 774 | 'managed settings may let the plugins a person installs override', |
| 775 | { plugins: [Fixtures.allowing('easy')] }, |
| 776 | async ($, on) => { |
| 777 | on('settings.read', () => ({ value: Fixtures.overridePolicyOf(true) })) |
| 778 | |
| 779 | const lines = Fixtures.logged(on) |
| 780 | const evaluations = Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 781 | |
| 782 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.ALLOWED) |
| 783 | |
| 784 | expect({ lines, evaluations: evaluations() }).toEqual({ |
| 785 | lines: [], |
| 786 | evaluations: 1, |
| 787 | }) |
| 788 | }, |
| 789 | ) |
| 790 | |
| 791 | test( |
| 792 | "only managed settings are asked: a person's own cannot lift a deny rule", |
| 793 | { plugins: [Fixtures.allowing('easy')] }, |
| 794 | async ($, on) => { |
| 795 | Fixtures.policyBySource( |
| 796 | on, |
| 797 | Fixtures.MANAGED_POLICY, |
| 798 | Fixtures.overridePolicyOf(true), |
| 799 | ) |
| 800 | |
| 801 | Fixtures.logged(on) |
| 802 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 803 | |
| 804 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 805 | }, |
| 806 | ) |
| 807 | |
| 808 | test( |
| 809 | 'the option mistyped lifts nothing: only the literal true does', |
| 810 | { plugins: [Fixtures.allowing('easy')] }, |
| 811 | async ($, on) => { |
| 812 | on('settings.read', () => ({ value: Fixtures.overridePolicyOf('true') })) |
| 813 | Fixtures.logged(on) |
| 814 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 815 | |
| 816 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.RULE_DENY) |
| 817 | }, |
| 818 | ) |
| 819 | |
| 820 | test( |
| 821 | 'with a policy that cannot be read the deny rule holds: fails closed', |
| 822 | { plugins: [Fixtures.allowing('easy')] }, |
| 823 | async ($, on) => { |
| 824 | const stopPolicy = Fixtures.policyUntilStopped( |
| 825 | on, |
| 826 | Fixtures.overridePolicyOf(true), |
| 827 | 'managed settings unreadable', |
| 828 | ) |
| 829 | |
| 830 | Fixtures.logged(on) |
| 831 | Fixtures.checksAnswered(on, Fixtures.RULE_DENY) |
| 832 | on('prompt.section', ($, e) => ({ text: e.text })) |
| 833 | |
| 834 | await $.prompt.section(Fixtures.MEMORY) |
| 835 | stopPolicy() |
| 836 | |
| 837 | expect( |
| 838 | await $.tool.check(Fixtures.CHECKED), |
| 839 | 'the policy that read let plugins override; unread, the rule holds', |
| 840 | ).toEqual(Fixtures.RULE_DENY) |
| 841 | }, |
| 842 | ) |
| 843 | |
| 844 | test( |
| 845 | "when the rules cannot be evaluated the call is refused: the hook's catch", |
| 846 | { plugins: [Fixtures.blindAllowing] }, |
| 847 | async ($, on) => { |
| 848 | on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) |
| 849 | Fixtures.logged(on) |
| 850 | |
| 851 | on('tool.check', () => { |
| 852 | throw new Error('the evaluation failed') |
| 853 | }) |
| 854 | |
| 855 | expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Hooks.UNCHECKED_DENY) |
| 856 | }, |
| 857 | ) |
| 858 | }) |