2
0

next.test.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. import { test, expect } from "bun:test"
  2. import { PermissionNext } from "../../src/permission/next"
  3. import { Instance } from "../../src/project/instance"
  4. import { Storage } from "../../src/storage/storage"
  5. import { tmpdir } from "../fixture/fixture"
  6. // fromConfig tests
  7. test("fromConfig - string value becomes wildcard rule", () => {
  8. const result = PermissionNext.fromConfig({ bash: "allow" })
  9. expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }])
  10. })
  11. test("fromConfig - object value converts to rules array", () => {
  12. const result = PermissionNext.fromConfig({ bash: { "*": "allow", rm: "deny" } })
  13. expect(result).toEqual([
  14. { permission: "bash", pattern: "*", action: "allow" },
  15. { permission: "bash", pattern: "rm", action: "deny" },
  16. ])
  17. })
  18. test("fromConfig - mixed string and object values", () => {
  19. const result = PermissionNext.fromConfig({
  20. bash: { "*": "allow", rm: "deny" },
  21. edit: "allow",
  22. webfetch: "ask",
  23. })
  24. expect(result).toEqual([
  25. { permission: "bash", pattern: "*", action: "allow" },
  26. { permission: "bash", pattern: "rm", action: "deny" },
  27. { permission: "edit", pattern: "*", action: "allow" },
  28. { permission: "webfetch", pattern: "*", action: "ask" },
  29. ])
  30. })
  31. test("fromConfig - empty object", () => {
  32. const result = PermissionNext.fromConfig({})
  33. expect(result).toEqual([])
  34. })
  35. // merge tests
  36. test("merge - simple concatenation", () => {
  37. const result = PermissionNext.merge(
  38. [{ permission: "bash", pattern: "*", action: "allow" }],
  39. [{ permission: "bash", pattern: "*", action: "deny" }],
  40. )
  41. expect(result).toEqual([
  42. { permission: "bash", pattern: "*", action: "allow" },
  43. { permission: "bash", pattern: "*", action: "deny" },
  44. ])
  45. })
  46. test("merge - adds new permission", () => {
  47. const result = PermissionNext.merge(
  48. [{ permission: "bash", pattern: "*", action: "allow" }],
  49. [{ permission: "edit", pattern: "*", action: "deny" }],
  50. )
  51. expect(result).toEqual([
  52. { permission: "bash", pattern: "*", action: "allow" },
  53. { permission: "edit", pattern: "*", action: "deny" },
  54. ])
  55. })
  56. test("merge - concatenates rules for same permission", () => {
  57. const result = PermissionNext.merge(
  58. [{ permission: "bash", pattern: "foo", action: "ask" }],
  59. [{ permission: "bash", pattern: "*", action: "deny" }],
  60. )
  61. expect(result).toEqual([
  62. { permission: "bash", pattern: "foo", action: "ask" },
  63. { permission: "bash", pattern: "*", action: "deny" },
  64. ])
  65. })
  66. test("merge - multiple rulesets", () => {
  67. const result = PermissionNext.merge(
  68. [{ permission: "bash", pattern: "*", action: "allow" }],
  69. [{ permission: "bash", pattern: "rm", action: "ask" }],
  70. [{ permission: "edit", pattern: "*", action: "allow" }],
  71. )
  72. expect(result).toEqual([
  73. { permission: "bash", pattern: "*", action: "allow" },
  74. { permission: "bash", pattern: "rm", action: "ask" },
  75. { permission: "edit", pattern: "*", action: "allow" },
  76. ])
  77. })
  78. test("merge - empty ruleset does nothing", () => {
  79. const result = PermissionNext.merge([{ permission: "bash", pattern: "*", action: "allow" }], [])
  80. expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }])
  81. })
  82. test("merge - preserves rule order", () => {
  83. const result = PermissionNext.merge(
  84. [
  85. { permission: "edit", pattern: "src/*", action: "allow" },
  86. { permission: "edit", pattern: "src/secret/*", action: "deny" },
  87. ],
  88. [{ permission: "edit", pattern: "src/secret/ok.ts", action: "allow" }],
  89. )
  90. expect(result).toEqual([
  91. { permission: "edit", pattern: "src/*", action: "allow" },
  92. { permission: "edit", pattern: "src/secret/*", action: "deny" },
  93. { permission: "edit", pattern: "src/secret/ok.ts", action: "allow" },
  94. ])
  95. })
  96. test("merge - config permission overrides default ask", () => {
  97. // Simulates: defaults have "*": "ask", config sets bash: "allow"
  98. const defaults: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }]
  99. const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
  100. const merged = PermissionNext.merge(defaults, config)
  101. // Config's bash allow should override default ask
  102. expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("allow")
  103. // Other permissions should still be ask (from defaults)
  104. expect(PermissionNext.evaluate("edit", "foo.ts", merged)).toBe("ask")
  105. })
  106. test("merge - config ask overrides default allow", () => {
  107. // Simulates: defaults have bash: "allow", config sets bash: "ask"
  108. const defaults: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
  109. const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "ask" }]
  110. const merged = PermissionNext.merge(defaults, config)
  111. // Config's ask should override default allow
  112. expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("ask")
  113. })
  114. // evaluate tests
  115. test("evaluate - exact pattern match", () => {
  116. const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "rm", action: "deny" }])
  117. expect(result).toBe("deny")
  118. })
  119. test("evaluate - wildcard pattern match", () => {
  120. const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "*", action: "allow" }])
  121. expect(result).toBe("allow")
  122. })
  123. test("evaluate - last matching rule wins", () => {
  124. const result = PermissionNext.evaluate("bash", "rm", [
  125. { permission: "bash", pattern: "*", action: "allow" },
  126. { permission: "bash", pattern: "rm", action: "deny" },
  127. ])
  128. expect(result).toBe("deny")
  129. })
  130. test("evaluate - last matching rule wins (wildcard after specific)", () => {
  131. const result = PermissionNext.evaluate("bash", "rm", [
  132. { permission: "bash", pattern: "rm", action: "deny" },
  133. { permission: "bash", pattern: "*", action: "allow" },
  134. ])
  135. expect(result).toBe("allow")
  136. })
  137. test("evaluate - glob pattern match", () => {
  138. const result = PermissionNext.evaluate("edit", "src/foo.ts", [
  139. { permission: "edit", pattern: "src/*", action: "allow" },
  140. ])
  141. expect(result).toBe("allow")
  142. })
  143. test("evaluate - last matching glob wins", () => {
  144. const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [
  145. { permission: "edit", pattern: "src/*", action: "deny" },
  146. { permission: "edit", pattern: "src/components/*", action: "allow" },
  147. ])
  148. expect(result).toBe("allow")
  149. })
  150. test("evaluate - order matters for specificity", () => {
  151. // If more specific rule comes first, later wildcard overrides it
  152. const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [
  153. { permission: "edit", pattern: "src/components/*", action: "allow" },
  154. { permission: "edit", pattern: "src/*", action: "deny" },
  155. ])
  156. expect(result).toBe("deny")
  157. })
  158. test("evaluate - unknown permission returns ask", () => {
  159. const result = PermissionNext.evaluate("unknown_tool", "anything", [
  160. { permission: "bash", pattern: "*", action: "allow" },
  161. ])
  162. expect(result).toBe("ask")
  163. })
  164. test("evaluate - empty ruleset returns ask", () => {
  165. const result = PermissionNext.evaluate("bash", "rm", [])
  166. expect(result).toBe("ask")
  167. })
  168. test("evaluate - no matching pattern returns ask", () => {
  169. const result = PermissionNext.evaluate("edit", "etc/passwd", [
  170. { permission: "edit", pattern: "src/*", action: "allow" },
  171. ])
  172. expect(result).toBe("ask")
  173. })
  174. test("evaluate - empty rules array returns ask", () => {
  175. const result = PermissionNext.evaluate("bash", "rm", [])
  176. expect(result).toBe("ask")
  177. })
  178. test("evaluate - multiple matching patterns, last wins", () => {
  179. const result = PermissionNext.evaluate("edit", "src/secret.ts", [
  180. { permission: "edit", pattern: "*", action: "ask" },
  181. { permission: "edit", pattern: "src/*", action: "allow" },
  182. { permission: "edit", pattern: "src/secret.ts", action: "deny" },
  183. ])
  184. expect(result).toBe("deny")
  185. })
  186. test("evaluate - non-matching patterns are skipped", () => {
  187. const result = PermissionNext.evaluate("edit", "src/foo.ts", [
  188. { permission: "edit", pattern: "*", action: "ask" },
  189. { permission: "edit", pattern: "test/*", action: "deny" },
  190. { permission: "edit", pattern: "src/*", action: "allow" },
  191. ])
  192. expect(result).toBe("allow")
  193. })
  194. test("evaluate - exact match at end wins over earlier wildcard", () => {
  195. const result = PermissionNext.evaluate("bash", "/bin/rm", [
  196. { permission: "bash", pattern: "*", action: "allow" },
  197. { permission: "bash", pattern: "/bin/rm", action: "deny" },
  198. ])
  199. expect(result).toBe("deny")
  200. })
  201. test("evaluate - wildcard at end overrides earlier exact match", () => {
  202. const result = PermissionNext.evaluate("bash", "/bin/rm", [
  203. { permission: "bash", pattern: "/bin/rm", action: "deny" },
  204. { permission: "bash", pattern: "*", action: "allow" },
  205. ])
  206. expect(result).toBe("allow")
  207. })
  208. // wildcard permission tests
  209. test("evaluate - wildcard permission matches any permission", () => {
  210. const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "*", action: "deny" }])
  211. expect(result).toBe("deny")
  212. })
  213. test("evaluate - wildcard permission with specific pattern", () => {
  214. const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "rm", action: "deny" }])
  215. expect(result).toBe("deny")
  216. })
  217. test("evaluate - glob permission pattern", () => {
  218. const result = PermissionNext.evaluate("mcp_server_tool", "anything", [
  219. { permission: "mcp_*", pattern: "*", action: "allow" },
  220. ])
  221. expect(result).toBe("allow")
  222. })
  223. test("evaluate - specific permission and wildcard permission combined", () => {
  224. const result = PermissionNext.evaluate("bash", "rm", [
  225. { permission: "*", pattern: "*", action: "deny" },
  226. { permission: "bash", pattern: "*", action: "allow" },
  227. ])
  228. expect(result).toBe("allow")
  229. })
  230. test("evaluate - wildcard permission does not match when specific exists", () => {
  231. const result = PermissionNext.evaluate("edit", "src/foo.ts", [
  232. { permission: "*", pattern: "*", action: "deny" },
  233. { permission: "edit", pattern: "src/*", action: "allow" },
  234. ])
  235. expect(result).toBe("allow")
  236. })
  237. test("evaluate - multiple matching permission patterns combine rules", () => {
  238. const result = PermissionNext.evaluate("mcp_dangerous", "anything", [
  239. { permission: "*", pattern: "*", action: "ask" },
  240. { permission: "mcp_*", pattern: "*", action: "allow" },
  241. { permission: "mcp_dangerous", pattern: "*", action: "deny" },
  242. ])
  243. expect(result).toBe("deny")
  244. })
  245. test("evaluate - wildcard permission fallback for unknown tool", () => {
  246. const result = PermissionNext.evaluate("unknown_tool", "anything", [
  247. { permission: "*", pattern: "*", action: "ask" },
  248. { permission: "bash", pattern: "*", action: "allow" },
  249. ])
  250. expect(result).toBe("ask")
  251. })
  252. test("evaluate - permission patterns sorted by length regardless of object order", () => {
  253. // specific permission listed before wildcard, but specific should still win
  254. const result = PermissionNext.evaluate("bash", "rm", [
  255. { permission: "bash", pattern: "*", action: "allow" },
  256. { permission: "*", pattern: "*", action: "deny" },
  257. ])
  258. // With flat list, last matching rule wins - so "*" matches bash and wins
  259. expect(result).toBe("deny")
  260. })
  261. test("evaluate - merges multiple rulesets", () => {
  262. const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
  263. const approved: PermissionNext.Ruleset = [{ permission: "bash", pattern: "rm", action: "deny" }]
  264. // approved comes after config, so rm should be denied
  265. const result = PermissionNext.evaluate("bash", "rm", config, approved)
  266. expect(result).toBe("deny")
  267. })
  268. // disabled tests
  269. test("disabled - returns empty set when all tools allowed", () => {
  270. const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "allow" }])
  271. expect(result.size).toBe(0)
  272. })
  273. test("disabled - disables tool when denied", () => {
  274. const result = PermissionNext.disabled(
  275. ["bash", "edit", "read"],
  276. [
  277. { permission: "*", pattern: "*", action: "allow" },
  278. { permission: "bash", pattern: "*", action: "deny" },
  279. ],
  280. )
  281. expect(result.has("bash")).toBe(true)
  282. expect(result.has("edit")).toBe(false)
  283. expect(result.has("read")).toBe(false)
  284. })
  285. test("disabled - disables edit/write/patch/multiedit when edit denied", () => {
  286. const result = PermissionNext.disabled(
  287. ["edit", "write", "patch", "multiedit", "bash"],
  288. [
  289. { permission: "*", pattern: "*", action: "allow" },
  290. { permission: "edit", pattern: "*", action: "deny" },
  291. ],
  292. )
  293. expect(result.has("edit")).toBe(true)
  294. expect(result.has("write")).toBe(true)
  295. expect(result.has("patch")).toBe(true)
  296. expect(result.has("multiedit")).toBe(true)
  297. expect(result.has("bash")).toBe(false)
  298. })
  299. test("disabled - does not disable when partially denied", () => {
  300. const result = PermissionNext.disabled(
  301. ["bash"],
  302. [
  303. { permission: "bash", pattern: "*", action: "allow" },
  304. { permission: "bash", pattern: "rm *", action: "deny" },
  305. ],
  306. )
  307. expect(result.has("bash")).toBe(false)
  308. })
  309. test("disabled - does not disable when action is ask", () => {
  310. const result = PermissionNext.disabled(["bash", "edit"], [{ permission: "*", pattern: "*", action: "ask" }])
  311. expect(result.size).toBe(0)
  312. })
  313. test("disabled - disables when wildcard deny even with specific allow", () => {
  314. // Tool is disabled because evaluate("bash", "*", ...) returns "deny"
  315. // The "echo *" allow rule doesn't match the "*" pattern we're checking
  316. const result = PermissionNext.disabled(
  317. ["bash"],
  318. [
  319. { permission: "bash", pattern: "*", action: "deny" },
  320. { permission: "bash", pattern: "echo *", action: "allow" },
  321. ],
  322. )
  323. expect(result.has("bash")).toBe(true)
  324. })
  325. test("disabled - does not disable when wildcard allow after deny", () => {
  326. const result = PermissionNext.disabled(
  327. ["bash"],
  328. [
  329. { permission: "bash", pattern: "rm *", action: "deny" },
  330. { permission: "bash", pattern: "*", action: "allow" },
  331. ],
  332. )
  333. expect(result.has("bash")).toBe(false)
  334. })
  335. test("disabled - disables multiple tools", () => {
  336. const result = PermissionNext.disabled(
  337. ["bash", "edit", "webfetch"],
  338. [
  339. { permission: "bash", pattern: "*", action: "deny" },
  340. { permission: "edit", pattern: "*", action: "deny" },
  341. { permission: "webfetch", pattern: "*", action: "deny" },
  342. ],
  343. )
  344. expect(result.has("bash")).toBe(true)
  345. expect(result.has("edit")).toBe(true)
  346. expect(result.has("webfetch")).toBe(true)
  347. })
  348. test("disabled - wildcard permission denies all tools", () => {
  349. const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "deny" }])
  350. expect(result.has("bash")).toBe(true)
  351. expect(result.has("edit")).toBe(true)
  352. expect(result.has("read")).toBe(true)
  353. })
  354. test("disabled - specific allow overrides wildcard deny", () => {
  355. const result = PermissionNext.disabled(
  356. ["bash", "edit", "read"],
  357. [
  358. { permission: "*", pattern: "*", action: "deny" },
  359. { permission: "bash", pattern: "*", action: "allow" },
  360. ],
  361. )
  362. expect(result.has("bash")).toBe(false)
  363. expect(result.has("edit")).toBe(true)
  364. expect(result.has("read")).toBe(true)
  365. })
  366. // ask tests
  367. test("ask - resolves immediately when action is allow", async () => {
  368. await using tmp = await tmpdir({ git: true })
  369. await Instance.provide({
  370. directory: tmp.path,
  371. fn: async () => {
  372. const result = await PermissionNext.ask({
  373. sessionID: "session_test",
  374. permission: "bash",
  375. patterns: ["ls"],
  376. metadata: {},
  377. always: [],
  378. ruleset: [{ permission: "bash", pattern: "*", action: "allow" }],
  379. })
  380. expect(result).toBeUndefined()
  381. },
  382. })
  383. })
  384. test("ask - throws RejectedError when action is deny", async () => {
  385. await using tmp = await tmpdir({ git: true })
  386. await Instance.provide({
  387. directory: tmp.path,
  388. fn: async () => {
  389. await expect(
  390. PermissionNext.ask({
  391. sessionID: "session_test",
  392. permission: "bash",
  393. patterns: ["rm -rf /"],
  394. metadata: {},
  395. always: [],
  396. ruleset: [{ permission: "bash", pattern: "*", action: "deny" }],
  397. }),
  398. ).rejects.toBeInstanceOf(PermissionNext.RejectedError)
  399. },
  400. })
  401. })
  402. test("ask - returns pending promise when action is ask", async () => {
  403. await using tmp = await tmpdir({ git: true })
  404. await Instance.provide({
  405. directory: tmp.path,
  406. fn: async () => {
  407. const promise = PermissionNext.ask({
  408. sessionID: "session_test",
  409. permission: "bash",
  410. patterns: ["ls"],
  411. metadata: {},
  412. always: [],
  413. ruleset: [{ permission: "bash", pattern: "*", action: "ask" }],
  414. })
  415. // Promise should be pending, not resolved
  416. expect(promise).toBeInstanceOf(Promise)
  417. // Don't await - just verify it returns a promise
  418. },
  419. })
  420. })
  421. // reply tests
  422. test("reply - once resolves the pending ask", async () => {
  423. await using tmp = await tmpdir({ git: true })
  424. await Instance.provide({
  425. directory: tmp.path,
  426. fn: async () => {
  427. const askPromise = PermissionNext.ask({
  428. id: "permission_test1",
  429. sessionID: "session_test",
  430. permission: "bash",
  431. patterns: ["ls"],
  432. metadata: {},
  433. always: [],
  434. ruleset: [],
  435. })
  436. await PermissionNext.reply({
  437. requestID: "permission_test1",
  438. reply: "once",
  439. })
  440. await expect(askPromise).resolves.toBeUndefined()
  441. },
  442. })
  443. })
  444. test("reply - reject throws RejectedError", async () => {
  445. await using tmp = await tmpdir({ git: true })
  446. await Instance.provide({
  447. directory: tmp.path,
  448. fn: async () => {
  449. const askPromise = PermissionNext.ask({
  450. id: "permission_test2",
  451. sessionID: "session_test",
  452. permission: "bash",
  453. patterns: ["ls"],
  454. metadata: {},
  455. always: [],
  456. ruleset: [],
  457. })
  458. await PermissionNext.reply({
  459. requestID: "permission_test2",
  460. reply: "reject",
  461. })
  462. await expect(askPromise).rejects.toBeInstanceOf(PermissionNext.RejectedError)
  463. },
  464. })
  465. })
  466. test("reply - always persists approval and resolves", async () => {
  467. await using tmp = await tmpdir({ git: true })
  468. await Instance.provide({
  469. directory: tmp.path,
  470. fn: async () => {
  471. const askPromise = PermissionNext.ask({
  472. id: "permission_test3",
  473. sessionID: "session_test",
  474. permission: "bash",
  475. patterns: ["ls"],
  476. metadata: {},
  477. always: ["ls"],
  478. ruleset: [],
  479. })
  480. await PermissionNext.reply({
  481. requestID: "permission_test3",
  482. reply: "always",
  483. })
  484. await expect(askPromise).resolves.toBeUndefined()
  485. },
  486. })
  487. // Re-provide to reload state with stored permissions
  488. await Instance.provide({
  489. directory: tmp.path,
  490. fn: async () => {
  491. // Stored approval should allow without asking
  492. const result = await PermissionNext.ask({
  493. sessionID: "session_test2",
  494. permission: "bash",
  495. patterns: ["ls"],
  496. metadata: {},
  497. always: [],
  498. ruleset: [],
  499. })
  500. expect(result).toBeUndefined()
  501. },
  502. })
  503. })
  504. test("reply - reject cancels all pending for same session", async () => {
  505. await using tmp = await tmpdir({ git: true })
  506. await Instance.provide({
  507. directory: tmp.path,
  508. fn: async () => {
  509. const askPromise1 = PermissionNext.ask({
  510. id: "permission_test4a",
  511. sessionID: "session_same",
  512. permission: "bash",
  513. patterns: ["ls"],
  514. metadata: {},
  515. always: [],
  516. ruleset: [],
  517. })
  518. const askPromise2 = PermissionNext.ask({
  519. id: "permission_test4b",
  520. sessionID: "session_same",
  521. permission: "edit",
  522. patterns: ["foo.ts"],
  523. metadata: {},
  524. always: [],
  525. ruleset: [],
  526. })
  527. // Catch rejections before they become unhandled
  528. const result1 = askPromise1.catch((e) => e)
  529. const result2 = askPromise2.catch((e) => e)
  530. // Reject the first one
  531. await PermissionNext.reply({
  532. requestID: "permission_test4a",
  533. reply: "reject",
  534. })
  535. // Both should be rejected
  536. expect(await result1).toBeInstanceOf(PermissionNext.RejectedError)
  537. expect(await result2).toBeInstanceOf(PermissionNext.RejectedError)
  538. },
  539. })
  540. })
  541. test("ask - checks all patterns and stops on first deny", async () => {
  542. await using tmp = await tmpdir({ git: true })
  543. await Instance.provide({
  544. directory: tmp.path,
  545. fn: async () => {
  546. await expect(
  547. PermissionNext.ask({
  548. sessionID: "session_test",
  549. permission: "bash",
  550. patterns: ["echo hello", "rm -rf /"],
  551. metadata: {},
  552. always: [],
  553. ruleset: [
  554. { permission: "bash", pattern: "*", action: "allow" },
  555. { permission: "bash", pattern: "rm *", action: "deny" },
  556. ],
  557. }),
  558. ).rejects.toBeInstanceOf(PermissionNext.RejectedError)
  559. },
  560. })
  561. })
  562. test("ask - allows all patterns when all match allow rules", async () => {
  563. await using tmp = await tmpdir({ git: true })
  564. await Instance.provide({
  565. directory: tmp.path,
  566. fn: async () => {
  567. const result = await PermissionNext.ask({
  568. sessionID: "session_test",
  569. permission: "bash",
  570. patterns: ["echo hello", "ls -la", "pwd"],
  571. metadata: {},
  572. always: [],
  573. ruleset: [{ permission: "bash", pattern: "*", action: "allow" }],
  574. })
  575. expect(result).toBeUndefined()
  576. },
  577. })
  578. })