@gotgenes/pi-permission-system 9.0.1 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,6 +98,22 @@ describe("formatDenyReason", () => {
98
98
  );
99
99
  });
100
100
 
101
+ test("bash with nested execution context", () => {
102
+ expect(
103
+ formatDenyReason(
104
+ toolCtx(
105
+ toolCheck("bash", {
106
+ command: "rm -rf foo",
107
+ matchedPattern: "rm *",
108
+ commandContext: "command_substitution",
109
+ }),
110
+ ),
111
+ ),
112
+ ).toBe(
113
+ "[pi-permission-system] is not permitted to run 'bash' command 'rm -rf foo' (matched 'rm *', inside command substitution).",
114
+ );
115
+ });
116
+
101
117
  test("MCP source with target on non-mcp toolName", () => {
102
118
  expect(
103
119
  formatDenyReason(
@@ -23,18 +23,17 @@ function bashResult(
23
23
  }
24
24
 
25
25
  describe("resolveBashCommandCheck", () => {
26
- it("passes a single command straight through", async () => {
27
- const decompose = vi.fn(async () => ["npm install pkg"]);
26
+ it("passes a single command straight through", () => {
28
27
  const checkPermission = vi
29
28
  .fn<CheckPermissionFn>()
30
29
  .mockReturnValue(bashResult("allow", "npm install pkg", "npm *"));
31
30
 
32
- const result = await resolveBashCommandCheck(
31
+ const result = resolveBashCommandCheck(
33
32
  "npm install pkg",
33
+ [{ text: "npm install pkg" }],
34
34
  undefined,
35
35
  [],
36
36
  checkPermission,
37
- decompose,
38
37
  );
39
38
 
40
39
  expect(result.state).toBe("allow");
@@ -47,8 +46,7 @@ describe("resolveBashCommandCheck", () => {
47
46
  );
48
47
  });
49
48
 
50
- it("denies the chain when any sub-command is denied, reporting that command's pattern", async () => {
51
- const decompose = vi.fn(async () => ["cd /p", "npm install pkg"]);
49
+ it("denies the chain when any sub-command is denied, reporting that command's pattern", () => {
52
50
  const checkPermission = vi
53
51
  .fn<CheckPermissionFn>()
54
52
  .mockImplementation((_surface, input) => {
@@ -58,12 +56,12 @@ describe("resolveBashCommandCheck", () => {
58
56
  : bashResult("allow", command, "cd *");
59
57
  });
60
58
 
61
- const result = await resolveBashCommandCheck(
59
+ const result = resolveBashCommandCheck(
62
60
  "cd /p && npm install pkg",
61
+ [{ text: "cd /p" }, { text: "npm install pkg" }],
63
62
  undefined,
64
63
  [],
65
64
  checkPermission,
66
- decompose,
67
65
  );
68
66
 
69
67
  expect(result.state).toBe("deny");
@@ -71,8 +69,7 @@ describe("resolveBashCommandCheck", () => {
71
69
  expect(result.command).toBe("npm install pkg");
72
70
  });
73
71
 
74
- it("asks when a sub-command asks and none denies", async () => {
75
- const decompose = vi.fn(async () => ["cd /p", "git push"]);
72
+ it("asks when a sub-command asks and none denies", () => {
76
73
  const checkPermission = vi
77
74
  .fn<CheckPermissionFn>()
78
75
  .mockImplementation((_surface, input) => {
@@ -82,12 +79,12 @@ describe("resolveBashCommandCheck", () => {
82
79
  : bashResult("allow", command, "cd *");
83
80
  });
84
81
 
85
- const result = await resolveBashCommandCheck(
82
+ const result = resolveBashCommandCheck(
86
83
  "cd /p && git push",
84
+ [{ text: "cd /p" }, { text: "git push" }],
87
85
  undefined,
88
86
  [],
89
87
  checkPermission,
90
- decompose,
91
88
  );
92
89
 
93
90
  expect(result.state).toBe("ask");
@@ -95,8 +92,7 @@ describe("resolveBashCommandCheck", () => {
95
92
  expect(result.command).toBe("git push");
96
93
  });
97
94
 
98
- it("returns the first allow result when every sub-command is allowed", async () => {
99
- const decompose = vi.fn(async () => ["a", "b"]);
95
+ it("returns the first allow result when every sub-command is allowed", () => {
100
96
  const checkPermission = vi
101
97
  .fn<CheckPermissionFn>()
102
98
  .mockImplementation((_surface, input) => {
@@ -104,33 +100,33 @@ describe("resolveBashCommandCheck", () => {
104
100
  return bashResult("allow", command, `${command} *`);
105
101
  });
106
102
 
107
- const result = await resolveBashCommandCheck(
103
+ const result = resolveBashCommandCheck(
108
104
  "a && b",
105
+ [{ text: "a" }, { text: "b" }],
109
106
  undefined,
110
107
  [],
111
108
  checkPermission,
112
- decompose,
113
109
  );
114
110
 
115
111
  expect(result.state).toBe("allow");
116
112
  expect(result.matchedPattern).toBe("a *");
117
113
  });
118
114
 
119
- it("falls back to the whole command when no top-level commands are found", async () => {
120
- const decompose = vi.fn(async () => []);
115
+ it("falls back to the whole command when no top-level commands are found", () => {
121
116
  const checkPermission = vi
122
117
  .fn<CheckPermissionFn>()
123
118
  .mockReturnValue(bashResult("ask", "( rm x )", "*"));
124
119
 
125
- const result = await resolveBashCommandCheck(
120
+ const result = resolveBashCommandCheck(
126
121
  "( rm x )",
122
+ [],
127
123
  undefined,
128
124
  [],
129
125
  checkPermission,
130
- decompose,
131
126
  );
132
127
 
133
128
  expect(result.state).toBe("ask");
129
+ expect(result.commandContext).toBeUndefined();
134
130
  expect(checkPermission).toHaveBeenCalledTimes(1);
135
131
  expect(checkPermission).toHaveBeenCalledWith(
136
132
  "bash",
@@ -140,21 +136,20 @@ describe("resolveBashCommandCheck", () => {
140
136
  );
141
137
  });
142
138
 
143
- it("forwards the agent name and session rules to each sub-command check", async () => {
139
+ it("forwards the agent name and session rules to each sub-command check", () => {
144
140
  const sessionRules: Rule[] = [
145
141
  { surface: "bash", pattern: "npm *", action: "allow", origin: "session" },
146
142
  ];
147
- const decompose = vi.fn(async () => ["npm i"]);
148
143
  const checkPermission = vi
149
144
  .fn<CheckPermissionFn>()
150
145
  .mockReturnValue(bashResult("allow", "npm i"));
151
146
 
152
- await resolveBashCommandCheck(
147
+ resolveBashCommandCheck(
153
148
  "npm i",
149
+ [{ text: "npm i" }],
154
150
  "agent-x",
155
151
  sessionRules,
156
152
  checkPermission,
157
- decompose,
158
153
  );
159
154
 
160
155
  expect(checkPermission).toHaveBeenCalledWith(
@@ -164,4 +159,47 @@ describe("resolveBashCommandCheck", () => {
164
159
  sessionRules,
165
160
  );
166
161
  });
162
+
163
+ it("tags the winning result with the offending command's execution context", () => {
164
+ const checkPermission = vi
165
+ .fn<CheckPermissionFn>()
166
+ .mockImplementation((_surface, input) => {
167
+ const command = (input as { command: string }).command;
168
+ return command.startsWith("rm")
169
+ ? bashResult("deny", command, "rm *")
170
+ : bashResult("allow", command, "echo *");
171
+ });
172
+
173
+ const result = resolveBashCommandCheck(
174
+ "echo $(rm -rf foo)",
175
+ [
176
+ { text: "echo $(rm -rf foo)" },
177
+ { text: "rm -rf foo", context: "command_substitution" },
178
+ ],
179
+ undefined,
180
+ [],
181
+ checkPermission,
182
+ );
183
+
184
+ expect(result.state).toBe("deny");
185
+ expect(result.command).toBe("rm -rf foo");
186
+ expect(result.commandContext).toBe("command_substitution");
187
+ });
188
+
189
+ it("leaves commandContext unset when the winning command is top-level", () => {
190
+ const checkPermission = vi
191
+ .fn<CheckPermissionFn>()
192
+ .mockReturnValue(bashResult("deny", "rm -rf foo", "rm *"));
193
+
194
+ const result = resolveBashCommandCheck(
195
+ "rm -rf foo",
196
+ [{ text: "rm -rf foo" }],
197
+ undefined,
198
+ [],
199
+ checkPermission,
200
+ );
201
+
202
+ expect(result.state).toBe("deny");
203
+ expect(result.commandContext).toBeUndefined();
204
+ });
167
205
  });
@@ -1,8 +1,11 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
+ import { getNonEmptyString, toRecord } from "#src/common";
2
3
  import { describeBashExternalDirectoryGate } from "#src/handlers/gates/bash-external-directory";
4
+ import { BashProgram } from "#src/handlers/gates/bash-program";
3
5
  import type {
4
6
  GateBypass,
5
7
  GateDescriptor,
8
+ GateResult,
6
9
  } from "#src/handlers/gates/descriptor";
7
10
  import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
8
11
  import type { ToolCallContext } from "#src/handlers/gates/types";
@@ -34,11 +37,34 @@ function makeCheckResult(
34
37
  };
35
38
  }
36
39
 
40
+ /**
41
+ * Mirror the handler's parse-once derivation: parse the bash command into a
42
+ * shared `BashProgram` and inject it, exactly as `permission-gate-handler.ts`
43
+ * does, so the gate is exercised through the production wiring.
44
+ */
45
+ async function describeGate(
46
+ tcc: ToolCallContext,
47
+ checkPermission: Parameters<typeof describeBashExternalDirectoryGate>[2],
48
+ getSessionRuleset: Parameters<typeof describeBashExternalDirectoryGate>[3],
49
+ ): Promise<GateResult> {
50
+ const command = getNonEmptyString(toRecord(tcc.input).command);
51
+ const bashProgram =
52
+ tcc.toolName === "bash" && command
53
+ ? await BashProgram.parse(command)
54
+ : null;
55
+ return describeBashExternalDirectoryGate(
56
+ tcc,
57
+ bashProgram,
58
+ checkPermission,
59
+ getSessionRuleset,
60
+ );
61
+ }
62
+
37
63
  // ── tests ──────────────────────────────────────────────────────────────────
38
64
 
39
65
  describe("describeBashExternalDirectoryGate", () => {
40
66
  it("returns null when tool is not bash", async () => {
41
- const result = await describeBashExternalDirectoryGate(
67
+ const result = await describeGate(
42
68
  makeTcc({ toolName: "read" }),
43
69
  vi.fn().mockReturnValue(makeCheckResult("ask")),
44
70
  vi.fn().mockReturnValue([]),
@@ -47,7 +73,7 @@ describe("describeBashExternalDirectoryGate", () => {
47
73
  });
48
74
 
49
75
  it("returns null when no CWD", async () => {
50
- const result = await describeBashExternalDirectoryGate(
76
+ const result = await describeGate(
51
77
  makeTcc({ cwd: undefined }),
52
78
  vi.fn().mockReturnValue(makeCheckResult("ask")),
53
79
  vi.fn().mockReturnValue([]),
@@ -56,7 +82,7 @@ describe("describeBashExternalDirectoryGate", () => {
56
82
  });
57
83
 
58
84
  it("returns null when command has no external paths", async () => {
59
- const result = await describeBashExternalDirectoryGate(
85
+ const result = await describeGate(
60
86
  makeTcc({ input: { command: "ls -la" } }),
61
87
  vi.fn().mockReturnValue(makeCheckResult("ask")),
62
88
  vi.fn().mockReturnValue([]),
@@ -68,7 +94,7 @@ describe("describeBashExternalDirectoryGate", () => {
68
94
  const checkPermission = vi
69
95
  .fn()
70
96
  .mockReturnValue(makeCheckResult("allow", { source: "session" }));
71
- const result = await describeBashExternalDirectoryGate(
97
+ const result = await describeGate(
72
98
  makeTcc(),
73
99
  checkPermission,
74
100
  vi.fn().mockReturnValue([]),
@@ -85,7 +111,7 @@ describe("describeBashExternalDirectoryGate", () => {
85
111
 
86
112
  it("returns GateDescriptor with multi-pattern sessionApproval for uncovered paths", async () => {
87
113
  const checkPermission = vi.fn().mockReturnValue(makeCheckResult("ask"));
88
- const result = await describeBashExternalDirectoryGate(
114
+ const result = await describeGate(
89
115
  makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
90
116
  checkPermission,
91
117
  vi.fn().mockReturnValue([]),
@@ -110,7 +136,7 @@ describe("describeBashExternalDirectoryGate", () => {
110
136
  return makeCheckResult("ask");
111
137
  },
112
138
  );
113
- const result = await describeBashExternalDirectoryGate(
139
+ const result = await describeGate(
114
140
  makeTcc(),
115
141
  checkPermission,
116
142
  vi.fn().mockReturnValue([]),
@@ -132,7 +158,7 @@ describe("describeBashExternalDirectoryGate", () => {
132
158
  return makeCheckResult("ask");
133
159
  },
134
160
  );
135
- const result = await describeBashExternalDirectoryGate(
161
+ const result = await describeGate(
136
162
  makeTcc(),
137
163
  checkPermission,
138
164
  vi.fn().mockReturnValue([]),
@@ -143,7 +169,7 @@ describe("describeBashExternalDirectoryGate", () => {
143
169
  });
144
170
 
145
171
  it("descriptor surface is 'external_directory'", async () => {
146
- const result = await describeBashExternalDirectoryGate(
172
+ const result = await describeGate(
147
173
  makeTcc(),
148
174
  vi.fn().mockReturnValue(makeCheckResult("ask")),
149
175
  vi.fn().mockReturnValue([]),
@@ -153,7 +179,7 @@ describe("describeBashExternalDirectoryGate", () => {
153
179
  });
154
180
 
155
181
  it("descriptor decision surface is 'external_directory'", async () => {
156
- const result = await describeBashExternalDirectoryGate(
182
+ const result = await describeGate(
157
183
  makeTcc(),
158
184
  vi.fn().mockReturnValue(makeCheckResult("ask")),
159
185
  vi.fn().mockReturnValue([]),
@@ -163,7 +189,7 @@ describe("describeBashExternalDirectoryGate", () => {
163
189
  });
164
190
 
165
191
  it("denialContext contains the command and external paths", async () => {
166
- const result = await describeBashExternalDirectoryGate(
192
+ const result = await describeGate(
167
193
  makeTcc({ input: { command: "cat /outside/file.ts" } }),
168
194
  vi.fn().mockReturnValue(makeCheckResult("ask")),
169
195
  vi.fn().mockReturnValue([]),
@@ -177,7 +203,7 @@ describe("describeBashExternalDirectoryGate", () => {
177
203
  });
178
204
 
179
205
  it("promptDetails includes command and tool_call source", async () => {
180
- const result = await describeBashExternalDirectoryGate(
206
+ const result = await describeGate(
181
207
  makeTcc({ agentName: "agent-1", toolCallId: "tc-5" }),
182
208
  vi.fn().mockReturnValue(makeCheckResult("ask")),
183
209
  vi.fn().mockReturnValue([]),
@@ -203,7 +229,7 @@ describe("describeBashExternalDirectoryGate", () => {
203
229
  return makeCheckResult("ask");
204
230
  },
205
231
  );
206
- const result = await describeBashExternalDirectoryGate(
232
+ const result = await describeGate(
207
233
  makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
208
234
  checkPermission,
209
235
  vi.fn().mockReturnValue([]),
@@ -227,7 +253,7 @@ describe("describeBashExternalDirectoryGate", () => {
227
253
  return makeCheckResult("ask");
228
254
  },
229
255
  );
230
- const result = await describeBashExternalDirectoryGate(
256
+ const result = await describeGate(
231
257
  makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
232
258
  checkPermission,
233
259
  vi.fn().mockReturnValue([]),
@@ -252,7 +278,7 @@ describe("describeBashExternalDirectoryGate", () => {
252
278
  return makeCheckResult("ask");
253
279
  },
254
280
  );
255
- const result = await describeBashExternalDirectoryGate(
281
+ const result = await describeGate(
256
282
  makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
257
283
  checkPermission,
258
284
  vi.fn().mockReturnValue([]),
@@ -9,12 +9,16 @@ vi.mock("node:os", () => {
9
9
  };
10
10
  });
11
11
 
12
+ import { getNonEmptyString, toRecord } from "#src/common";
12
13
  import { describeBashPathGate } from "#src/handlers/gates/bash-path";
14
+ import { BashProgram } from "#src/handlers/gates/bash-program";
13
15
  import type {
14
16
  GateBypass,
15
17
  GateDescriptor,
18
+ GateResult,
16
19
  } from "#src/handlers/gates/descriptor";
17
20
  import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
21
+ import type { ToolCallContext } from "#src/handlers/gates/types";
18
22
  import type { Rule } from "#src/rule";
19
23
  import type { PermissionCheckResult } from "#src/types";
20
24
 
@@ -34,13 +38,36 @@ type CheckPermissionFn = (
34
38
  sessionRules?: Rule[],
35
39
  ) => PermissionCheckResult;
36
40
 
41
+ /**
42
+ * Mirror the handler's parse-once derivation: parse the bash command into a
43
+ * shared `BashProgram` and inject it, exactly as `permission-gate-handler.ts`
44
+ * does, so the gate is exercised through the production wiring.
45
+ */
46
+ async function describeGate(
47
+ tcc: ToolCallContext,
48
+ checkPermission: CheckPermissionFn,
49
+ getSessionRuleset: () => Rule[],
50
+ ): Promise<GateResult> {
51
+ const command = getNonEmptyString(toRecord(tcc.input).command);
52
+ const bashProgram =
53
+ tcc.toolName === "bash" && command
54
+ ? await BashProgram.parse(command)
55
+ : null;
56
+ return describeBashPathGate(
57
+ tcc,
58
+ bashProgram,
59
+ checkPermission,
60
+ getSessionRuleset,
61
+ );
62
+ }
63
+
37
64
  // ── tests ──────────────────────────────────────────────────────────────────
38
65
 
39
66
  describe("describeBashPathGate", () => {
40
67
  it("returns null for non-bash tools", async () => {
41
68
  const checkPermission = vi.fn<CheckPermissionFn>();
42
69
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
43
- const result = await describeBashPathGate(
70
+ const result = await describeGate(
44
71
  makeTcc({ toolName: "read", input: { path: ".env" } }),
45
72
  checkPermission,
46
73
  getSessionRuleset,
@@ -51,7 +78,7 @@ describe("describeBashPathGate", () => {
51
78
  it("returns null when no tokens are extracted", async () => {
52
79
  const checkPermission = vi.fn<CheckPermissionFn>();
53
80
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
54
- const result = await describeBashPathGate(
81
+ const result = await describeGate(
55
82
  makeTcc({ input: { command: "echo hello" } }),
56
83
  checkPermission,
57
84
  getSessionRuleset,
@@ -64,7 +91,7 @@ describe("describeBashPathGate", () => {
64
91
  .fn<CheckPermissionFn>()
65
92
  .mockReturnValue(makeCheckResult({ state: "allow" }));
66
93
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
67
- const result = await describeBashPathGate(
94
+ const result = await describeGate(
68
95
  makeTcc({ input: { command: "cat .env" } }),
69
96
  checkPermission,
70
97
  getSessionRuleset,
@@ -80,7 +107,7 @@ describe("describeBashPathGate", () => {
80
107
  }),
81
108
  );
82
109
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
83
- const result = await describeBashPathGate(
110
+ const result = await describeGate(
84
111
  makeTcc({ input: { command: "cat .env" } }),
85
112
  checkPermission,
86
113
  getSessionRuleset,
@@ -97,7 +124,7 @@ describe("describeBashPathGate", () => {
97
124
  .fn<CheckPermissionFn>()
98
125
  .mockReturnValue(makeCheckResult({ state: "ask", matchedPattern: "*" }));
99
126
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
100
- const result = await describeBashPathGate(
127
+ const result = await describeGate(
101
128
  makeTcc({ input: { command: "cat .env" } }),
102
129
  checkPermission,
103
130
  getSessionRuleset,
@@ -115,7 +142,7 @@ describe("describeBashPathGate", () => {
115
142
  makeCheckResult({ state: "deny", matchedPattern: "*.env" }),
116
143
  );
117
144
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
118
- const result = (await describeBashPathGate(
145
+ const result = (await describeGate(
119
146
  makeTcc({ input: { command: "cat .env" } }),
120
147
  checkPermission,
121
148
  getSessionRuleset,
@@ -135,7 +162,7 @@ describe("describeBashPathGate", () => {
135
162
  makeCheckResult({ state: "deny", matchedPattern: "*.env" }),
136
163
  );
137
164
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
138
- const result = (await describeBashPathGate(
165
+ const result = (await describeGate(
139
166
  makeTcc({ input: { command: "cat .env" } }),
140
167
  checkPermission,
141
168
  getSessionRuleset,
@@ -156,7 +183,7 @@ describe("describeBashPathGate", () => {
156
183
  origin: "session",
157
184
  },
158
185
  ]);
159
- const result = await describeBashPathGate(
186
+ const result = await describeGate(
160
187
  makeTcc({ input: { command: "cat .env" } }),
161
188
  checkPermission,
162
189
  getSessionRuleset,
@@ -169,7 +196,7 @@ describe("describeBashPathGate", () => {
169
196
  it("returns null when command is missing", async () => {
170
197
  const checkPermission = vi.fn<CheckPermissionFn>();
171
198
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
172
- const result = await describeBashPathGate(
199
+ const result = await describeGate(
173
200
  makeTcc({ input: {} }),
174
201
  checkPermission,
175
202
  getSessionRuleset,
@@ -188,7 +215,7 @@ describe("describeBashPathGate", () => {
188
215
  return makeCheckResult({ state: "deny", matchedPattern: "*.env" });
189
216
  });
190
217
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
191
- const result = await describeBashPathGate(
218
+ const result = await describeGate(
192
219
  makeTcc({ input: { command: "cat src/foo.ts .env" } }),
193
220
  checkPermission,
194
221
  getSessionRuleset,
@@ -209,7 +236,7 @@ describe("describeBashPathGate", () => {
209
236
  return makeCheckResult({ state: "allow" });
210
237
  });
211
238
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
212
- const result = await describeBashPathGate(
239
+ const result = await describeGate(
213
240
  makeTcc({ input: { command: "cp .env README.md" } }),
214
241
  checkPermission,
215
242
  getSessionRuleset,
@@ -232,7 +259,7 @@ describe("describeBashPathGate", () => {
232
259
  return makeCheckResult({ state: "allow" });
233
260
  });
234
261
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
235
- const result = await describeBashPathGate(
262
+ const result = await describeGate(
236
263
  makeTcc({ input: { command: "echo test > .env" } }),
237
264
  checkPermission,
238
265
  getSessionRuleset,
@@ -252,7 +279,7 @@ describe("describeBashPathGate", () => {
252
279
  }),
253
280
  );
254
281
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
255
- const result = await describeBashPathGate(
282
+ const result = await describeGate(
256
283
  makeTcc({ input: { command: "cat .env" } }),
257
284
  checkPermission,
258
285
  getSessionRuleset,
@@ -280,7 +307,7 @@ describe("describeBashPathGate", () => {
280
307
  });
281
308
  });
282
309
  const getSessionRuleset = vi.fn<() => Rule[]>().mockReturnValue([]);
283
- const result = await describeBashPathGate(
310
+ const result = await describeGate(
284
311
  makeTcc({ input: { command: "cat src/foo.ts .env" } }),
285
312
  checkPermission,
286
313
  getSessionRuleset,