@gotgenes/pi-permission-system 4.8.0 → 5.0.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.
- package/CHANGELOG.md +43 -0
- package/README.md +18 -5
- package/config/config.example.json +2 -0
- package/package.json +1 -1
- package/schemas/permissions.schema.json +10 -0
- package/src/config-modal.ts +25 -3
- package/src/extension-config.ts +13 -1
- package/src/external-directory.ts +96 -1
- package/src/handlers/tool-call.ts +87 -61
- package/src/index.ts +4 -0
- package/src/normalize.ts +2 -2
- package/src/permission-manager.ts +72 -17
- package/src/rule.ts +26 -2
- package/src/runtime.ts +17 -0
- package/src/session-rules.ts +7 -1
- package/src/synthesize.ts +7 -2
- package/src/tool-input-preview.ts +7 -1
- package/src/types.ts +6 -0
- package/tests/bash-external-directory.test.ts +50 -0
- package/tests/config-modal.test.ts +83 -0
- package/tests/handlers/tool-call.test.ts +149 -1
- package/tests/normalize.test.ts +64 -22
- package/tests/permission-manager-unified.test.ts +215 -0
- package/tests/permission-prompts.test.ts +8 -1
- package/tests/permission-system.test.ts +12 -0
- package/tests/pi-infrastructure-read.test.ts +245 -0
- package/tests/rule.test.ts +76 -8
- package/tests/runtime.test.ts +45 -0
- package/tests/session-rules.test.ts +7 -1
- package/tests/skill-prompt-sanitizer.test.ts +1 -1
- package/tests/synthesize.test.ts +64 -4
- package/tests/tool-input-preview.test.ts +29 -0
package/tests/synthesize.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
+
import type { RuleOrigin } from "../src/rule";
|
|
2
3
|
import { evaluate } from "../src/rule";
|
|
3
4
|
import {
|
|
4
5
|
composeRuleset,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
// ── synthesizeDefaults ─────────────────────────────────────────────────────
|
|
10
11
|
|
|
11
12
|
describe("synthesizeDefaults", () => {
|
|
12
|
-
test("emits a single universal catch-all rule with layer 'default'", () => {
|
|
13
|
+
test("emits a single universal catch-all rule with layer 'default' and origin 'builtin'", () => {
|
|
13
14
|
const rules = synthesizeDefaults("ask");
|
|
14
15
|
expect(rules).toHaveLength(1);
|
|
15
16
|
expect(rules[0]).toEqual({
|
|
@@ -17,6 +18,7 @@ describe("synthesizeDefaults", () => {
|
|
|
17
18
|
pattern: "*",
|
|
18
19
|
action: "ask",
|
|
19
20
|
layer: "default",
|
|
21
|
+
origin: "builtin",
|
|
20
22
|
});
|
|
21
23
|
});
|
|
22
24
|
|
|
@@ -38,6 +40,39 @@ describe("synthesizeDefaults", () => {
|
|
|
38
40
|
const rules = synthesizeDefaults("allow");
|
|
39
41
|
expect(evaluate("read", "*", rules).layer).toBe("default");
|
|
40
42
|
});
|
|
43
|
+
|
|
44
|
+
test("defaults to origin 'builtin' when no origin supplied", () => {
|
|
45
|
+
const rules = synthesizeDefaults("ask");
|
|
46
|
+
expect(rules[0].origin).toBe("builtin");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("universal rule carries config scope origin when supplied", () => {
|
|
50
|
+
const origin: RuleOrigin = "global";
|
|
51
|
+
const rules = synthesizeDefaults("ask", origin);
|
|
52
|
+
expect(rules[0].origin).toBe("global");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("origin is preserved through evaluate()", () => {
|
|
56
|
+
const rules = synthesizeDefaults("allow", "project");
|
|
57
|
+
const result = evaluate("read", "*", rules);
|
|
58
|
+
expect(result.origin).toBe("project");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("all RuleOrigin values are accepted", () => {
|
|
62
|
+
const origins: RuleOrigin[] = [
|
|
63
|
+
"global",
|
|
64
|
+
"project",
|
|
65
|
+
"agent",
|
|
66
|
+
"project-agent",
|
|
67
|
+
"builtin",
|
|
68
|
+
"baseline",
|
|
69
|
+
"session",
|
|
70
|
+
];
|
|
71
|
+
for (const origin of origins) {
|
|
72
|
+
const rules = synthesizeDefaults("ask", origin);
|
|
73
|
+
expect(rules[0].origin).toBe(origin);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
41
76
|
});
|
|
42
77
|
|
|
43
78
|
// ── synthesizeBaseline ─────────────────────────────────────────────────────
|
|
@@ -50,6 +85,7 @@ describe("synthesizeBaseline", () => {
|
|
|
50
85
|
pattern: "*",
|
|
51
86
|
action: "deny" as const,
|
|
52
87
|
layer: "config" as const,
|
|
88
|
+
origin: "global" as const,
|
|
53
89
|
},
|
|
54
90
|
];
|
|
55
91
|
expect(synthesizeBaseline(configRules)).toEqual([]);
|
|
@@ -66,19 +102,21 @@ describe("synthesizeBaseline", () => {
|
|
|
66
102
|
pattern: "exa:*",
|
|
67
103
|
action: "allow" as const,
|
|
68
104
|
layer: "config" as const,
|
|
105
|
+
origin: "global" as const,
|
|
69
106
|
},
|
|
70
107
|
];
|
|
71
108
|
const rules = synthesizeBaseline(configRules);
|
|
72
109
|
expect(rules).toHaveLength(5);
|
|
73
110
|
});
|
|
74
111
|
|
|
75
|
-
test("baseline rules all have layer 'baseline'
|
|
112
|
+
test("baseline rules all have layer 'baseline', action 'allow', and origin 'baseline'", () => {
|
|
76
113
|
const configRules = [
|
|
77
114
|
{
|
|
78
115
|
surface: "mcp",
|
|
79
116
|
pattern: "exa:*",
|
|
80
117
|
action: "allow" as const,
|
|
81
118
|
layer: "config" as const,
|
|
119
|
+
origin: "global" as const,
|
|
82
120
|
},
|
|
83
121
|
];
|
|
84
122
|
const rules = synthesizeBaseline(configRules);
|
|
@@ -86,6 +124,7 @@ describe("synthesizeBaseline", () => {
|
|
|
86
124
|
expect(rule.layer).toBe("baseline");
|
|
87
125
|
expect(rule.action).toBe("allow");
|
|
88
126
|
expect(rule.surface).toBe("mcp");
|
|
127
|
+
expect(rule.origin).toBe("baseline");
|
|
89
128
|
}
|
|
90
129
|
});
|
|
91
130
|
|
|
@@ -96,6 +135,7 @@ describe("synthesizeBaseline", () => {
|
|
|
96
135
|
pattern: "exa:*",
|
|
97
136
|
action: "allow" as const,
|
|
98
137
|
layer: "config" as const,
|
|
138
|
+
origin: "global" as const,
|
|
99
139
|
},
|
|
100
140
|
];
|
|
101
141
|
const rules = synthesizeBaseline(configRules);
|
|
@@ -114,6 +154,7 @@ describe("synthesizeBaseline", () => {
|
|
|
114
154
|
pattern: "git *",
|
|
115
155
|
action: "allow" as const,
|
|
116
156
|
layer: "config" as const,
|
|
157
|
+
origin: "global" as const,
|
|
117
158
|
},
|
|
118
159
|
];
|
|
119
160
|
expect(synthesizeBaseline(configRules)).toEqual([]);
|
|
@@ -126,12 +167,14 @@ describe("synthesizeBaseline", () => {
|
|
|
126
167
|
pattern: "exa:*",
|
|
127
168
|
action: "allow" as const,
|
|
128
169
|
layer: "config" as const,
|
|
170
|
+
origin: "global" as const,
|
|
129
171
|
},
|
|
130
172
|
];
|
|
131
173
|
const rules = synthesizeBaseline(configRules);
|
|
132
174
|
const result = evaluate("mcp", "mcp_status", rules);
|
|
133
175
|
expect(result.action).toBe("allow");
|
|
134
176
|
expect(result.layer).toBe("baseline");
|
|
177
|
+
expect(result.origin).toBe("baseline");
|
|
135
178
|
});
|
|
136
179
|
});
|
|
137
180
|
|
|
@@ -141,10 +184,21 @@ describe("composeRuleset", () => {
|
|
|
141
184
|
test("returns concatenation of all layers in order", () => {
|
|
142
185
|
const defaults = synthesizeDefaults("ask");
|
|
143
186
|
const baseline = synthesizeBaseline([
|
|
144
|
-
{
|
|
187
|
+
{
|
|
188
|
+
surface: "mcp",
|
|
189
|
+
pattern: "exa:*",
|
|
190
|
+
action: "allow",
|
|
191
|
+
layer: "config",
|
|
192
|
+
origin: "global" as const,
|
|
193
|
+
},
|
|
145
194
|
]);
|
|
146
195
|
const config = [
|
|
147
|
-
{
|
|
196
|
+
{
|
|
197
|
+
surface: "bash",
|
|
198
|
+
pattern: "rm -rf *",
|
|
199
|
+
action: "deny" as const,
|
|
200
|
+
origin: "global" as const,
|
|
201
|
+
},
|
|
148
202
|
];
|
|
149
203
|
const composed = composeRuleset(defaults, baseline, config);
|
|
150
204
|
expect(composed.length).toBe(
|
|
@@ -160,6 +214,7 @@ describe("composeRuleset", () => {
|
|
|
160
214
|
pattern: "*",
|
|
161
215
|
action: "deny" as const,
|
|
162
216
|
layer: "config" as const,
|
|
217
|
+
origin: "global" as const,
|
|
163
218
|
},
|
|
164
219
|
];
|
|
165
220
|
const composed = composeRuleset(defaults, [], config);
|
|
@@ -176,6 +231,7 @@ describe("composeRuleset", () => {
|
|
|
176
231
|
pattern: "*",
|
|
177
232
|
action: "allow" as const,
|
|
178
233
|
layer: "config" as const,
|
|
234
|
+
origin: "global" as const,
|
|
179
235
|
},
|
|
180
236
|
];
|
|
181
237
|
const composed = composeRuleset(defaults, [], config);
|
|
@@ -192,6 +248,7 @@ describe("composeRuleset", () => {
|
|
|
192
248
|
pattern: "mcp_status",
|
|
193
249
|
action: "allow" as const,
|
|
194
250
|
layer: "baseline" as const,
|
|
251
|
+
origin: "baseline" as const,
|
|
195
252
|
},
|
|
196
253
|
];
|
|
197
254
|
const config = [
|
|
@@ -200,6 +257,7 @@ describe("composeRuleset", () => {
|
|
|
200
257
|
pattern: "mcp_status",
|
|
201
258
|
action: "deny" as const,
|
|
202
259
|
layer: "config" as const,
|
|
260
|
+
origin: "global" as const,
|
|
203
261
|
},
|
|
204
262
|
];
|
|
205
263
|
const composed = composeRuleset(defaults, baseline, config);
|
|
@@ -216,6 +274,7 @@ describe("composeRuleset", () => {
|
|
|
216
274
|
pattern: "mcp_status",
|
|
217
275
|
action: "allow" as const,
|
|
218
276
|
layer: "baseline" as const,
|
|
277
|
+
origin: "baseline" as const,
|
|
219
278
|
},
|
|
220
279
|
];
|
|
221
280
|
const config = [
|
|
@@ -224,6 +283,7 @@ describe("composeRuleset", () => {
|
|
|
224
283
|
pattern: "exa_web_search",
|
|
225
284
|
action: "allow" as const,
|
|
226
285
|
layer: "config" as const,
|
|
286
|
+
origin: "global" as const,
|
|
227
287
|
},
|
|
228
288
|
];
|
|
229
289
|
const composed = composeRuleset(defaults, baseline, config);
|
|
@@ -365,6 +365,7 @@ describe("getToolInputPreviewForLog", () => {
|
|
|
365
365
|
toolName: "bash",
|
|
366
366
|
state: "allow",
|
|
367
367
|
source: "tool",
|
|
368
|
+
origin: "builtin",
|
|
368
369
|
};
|
|
369
370
|
expect(
|
|
370
371
|
getToolInputPreviewForLog(result, { command: "ls" }, pathBearingTools),
|
|
@@ -376,6 +377,7 @@ describe("getToolInputPreviewForLog", () => {
|
|
|
376
377
|
toolName: "mcp",
|
|
377
378
|
state: "allow",
|
|
378
379
|
source: "tool",
|
|
380
|
+
origin: "builtin",
|
|
379
381
|
};
|
|
380
382
|
expect(
|
|
381
383
|
getToolInputPreviewForLog(result, {}, pathBearingTools),
|
|
@@ -387,6 +389,7 @@ describe("getToolInputPreviewForLog", () => {
|
|
|
387
389
|
toolName: "some-server:some-tool",
|
|
388
390
|
state: "allow",
|
|
389
391
|
source: "mcp",
|
|
392
|
+
origin: "builtin",
|
|
390
393
|
};
|
|
391
394
|
expect(
|
|
392
395
|
getToolInputPreviewForLog(result, {}, pathBearingTools),
|
|
@@ -398,6 +401,7 @@ describe("getToolInputPreviewForLog", () => {
|
|
|
398
401
|
toolName: "read",
|
|
399
402
|
state: "allow",
|
|
400
403
|
source: "tool",
|
|
404
|
+
origin: "builtin",
|
|
401
405
|
};
|
|
402
406
|
const preview = getToolInputPreviewForLog(
|
|
403
407
|
result,
|
|
@@ -413,6 +417,7 @@ describe("getToolInputPreviewForLog", () => {
|
|
|
413
417
|
toolName: "task",
|
|
414
418
|
state: "allow",
|
|
415
419
|
source: "tool",
|
|
420
|
+
origin: "builtin",
|
|
416
421
|
};
|
|
417
422
|
const preview = getToolInputPreviewForLog(
|
|
418
423
|
result,
|
|
@@ -431,6 +436,7 @@ describe("getPermissionLogContext", () => {
|
|
|
431
436
|
toolName: "bash",
|
|
432
437
|
state: "allow",
|
|
433
438
|
source: "tool",
|
|
439
|
+
origin: "builtin",
|
|
434
440
|
command: "ls -la",
|
|
435
441
|
};
|
|
436
442
|
const ctx = getPermissionLogContext(result, {}, pathBearingTools);
|
|
@@ -444,6 +450,7 @@ describe("getPermissionLogContext", () => {
|
|
|
444
450
|
toolName: "read",
|
|
445
451
|
state: "allow",
|
|
446
452
|
source: "tool",
|
|
453
|
+
origin: "builtin",
|
|
447
454
|
};
|
|
448
455
|
const ctx = getPermissionLogContext(
|
|
449
456
|
result,
|
|
@@ -452,4 +459,26 @@ describe("getPermissionLogContext", () => {
|
|
|
452
459
|
);
|
|
453
460
|
expect(ctx.toolInputPreview).toContain("/foo.ts");
|
|
454
461
|
});
|
|
462
|
+
|
|
463
|
+
test("includes origin from check result when present", () => {
|
|
464
|
+
const result: PermissionCheckResult = {
|
|
465
|
+
toolName: "read",
|
|
466
|
+
state: "allow",
|
|
467
|
+
source: "tool",
|
|
468
|
+
origin: "project",
|
|
469
|
+
};
|
|
470
|
+
const ctx = getPermissionLogContext(result, {}, pathBearingTools);
|
|
471
|
+
expect(ctx.origin).toBe("project");
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("origin is 'builtin' when check result has builtin origin", () => {
|
|
475
|
+
const result: PermissionCheckResult = {
|
|
476
|
+
toolName: "read",
|
|
477
|
+
state: "allow",
|
|
478
|
+
source: "tool",
|
|
479
|
+
origin: "builtin",
|
|
480
|
+
};
|
|
481
|
+
const ctx = getPermissionLogContext(result, {}, pathBearingTools);
|
|
482
|
+
expect(ctx.origin).toBe("builtin");
|
|
483
|
+
});
|
|
455
484
|
});
|