@gotgenes/pi-permission-system 3.10.0 → 4.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 +55 -0
- package/README.md +135 -168
- package/config/config.example.json +11 -21
- package/package.json +1 -1
- package/schemas/permissions.schema.json +34 -102
- package/src/config-loader.ts +87 -118
- package/src/defaults.ts +6 -56
- package/src/extension-config.ts +3 -4
- package/src/handlers/tool-call.ts +15 -18
- package/src/normalize.ts +22 -60
- package/src/permission-manager.ts +309 -431
- package/src/rule.ts +5 -0
- package/src/session-rules.ts +1 -1
- package/src/synthesize.ts +87 -0
- package/src/types.ts +13 -19
- package/tests/config-loader.test.ts +113 -63
- package/tests/defaults.test.ts +8 -101
- package/tests/extension-config.test.ts +12 -4
- package/tests/normalize.test.ts +67 -64
- package/tests/permission-system.test.ts +310 -677
- package/tests/rule.test.ts +31 -0
- package/tests/session-rules.test.ts +1 -0
- package/tests/session-start.test.ts +1 -7
- package/tests/synthesize.test.ts +240 -0
package/tests/normalize.test.ts
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeFlatConfig } from "../src/normalize";
|
|
3
3
|
|
|
4
|
-
describe("
|
|
5
|
-
describe("
|
|
6
|
-
test("
|
|
7
|
-
const result =
|
|
8
|
-
tools: { read: "allow", write: "deny" },
|
|
9
|
-
});
|
|
4
|
+
describe("normalizeFlatConfig", () => {
|
|
5
|
+
describe("string shorthand", () => {
|
|
6
|
+
test("string value produces a single catch-all rule for the surface", () => {
|
|
7
|
+
const result = normalizeFlatConfig({ read: "allow" });
|
|
10
8
|
expect(result).toEqual([
|
|
11
9
|
{ surface: "read", pattern: "*", action: "allow" },
|
|
12
|
-
{ surface: "write", pattern: "*", action: "deny" },
|
|
13
10
|
]);
|
|
14
11
|
});
|
|
15
12
|
|
|
16
|
-
test("
|
|
17
|
-
const result =
|
|
18
|
-
tools: { bash: "allow", read: "allow" },
|
|
19
|
-
});
|
|
13
|
+
test("string shorthand works for multiple surfaces", () => {
|
|
14
|
+
const result = normalizeFlatConfig({ read: "allow", write: "deny" });
|
|
20
15
|
expect(result).toEqual([
|
|
21
16
|
{ surface: "read", pattern: "*", action: "allow" },
|
|
17
|
+
{ surface: "write", pattern: "*", action: "deny" },
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("universal fallback '*' becomes a catch-all rule with surface '*'", () => {
|
|
22
|
+
const result = normalizeFlatConfig({ "*": "ask" });
|
|
23
|
+
expect(result).toEqual([{ surface: "*", pattern: "*", action: "ask" }]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("external_directory string shorthand maps directly to its surface", () => {
|
|
27
|
+
const result = normalizeFlatConfig({ external_directory: "ask" });
|
|
28
|
+
expect(result).toEqual([
|
|
29
|
+
{ surface: "external_directory", pattern: "*", action: "ask" },
|
|
22
30
|
]);
|
|
23
31
|
});
|
|
24
32
|
|
|
25
|
-
test("
|
|
26
|
-
const result =
|
|
27
|
-
|
|
33
|
+
test("invalid string values (non-PermissionState) are ignored", () => {
|
|
34
|
+
const result = normalizeFlatConfig({
|
|
35
|
+
read: "allow",
|
|
36
|
+
write: "invalid" as never,
|
|
28
37
|
});
|
|
29
38
|
expect(result).toEqual([
|
|
30
39
|
{ surface: "read", pattern: "*", action: "allow" },
|
|
@@ -32,90 +41,84 @@ describe("normalizeConfig", () => {
|
|
|
32
41
|
});
|
|
33
42
|
});
|
|
34
43
|
|
|
35
|
-
describe("
|
|
36
|
-
test("
|
|
37
|
-
const result =
|
|
38
|
-
bash: { "
|
|
44
|
+
describe("object pattern map", () => {
|
|
45
|
+
test("object value produces one rule per pattern", () => {
|
|
46
|
+
const result = normalizeFlatConfig({
|
|
47
|
+
bash: { "*": "ask", "git *": "allow" },
|
|
39
48
|
});
|
|
40
49
|
expect(result).toEqual([
|
|
50
|
+
{ surface: "bash", pattern: "*", action: "ask" },
|
|
41
51
|
{ surface: "bash", pattern: "git *", action: "allow" },
|
|
42
|
-
{ surface: "bash", pattern: "rm -rf *", action: "deny" },
|
|
43
52
|
]);
|
|
44
53
|
});
|
|
45
|
-
});
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
mcp: { "exa:*": "allow", mcp_status: "allow" },
|
|
55
|
+
test("mcp object map produces rules with surface 'mcp'", () => {
|
|
56
|
+
const result = normalizeFlatConfig({
|
|
57
|
+
mcp: { "*": "ask", mcp_status: "allow" },
|
|
51
58
|
});
|
|
52
59
|
expect(result).toEqual([
|
|
53
|
-
{ surface: "mcp", pattern: "
|
|
60
|
+
{ surface: "mcp", pattern: "*", action: "ask" },
|
|
54
61
|
{ surface: "mcp", pattern: "mcp_status", action: "allow" },
|
|
55
62
|
]);
|
|
56
63
|
});
|
|
57
|
-
});
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
skills: { "*": "ask", librarian: "allow" },
|
|
65
|
+
test("skill object map produces rules with surface 'skill'", () => {
|
|
66
|
+
const result = normalizeFlatConfig({
|
|
67
|
+
skill: { "*": "ask", librarian: "allow" },
|
|
63
68
|
});
|
|
64
69
|
expect(result).toEqual([
|
|
65
70
|
{ surface: "skill", pattern: "*", action: "ask" },
|
|
66
71
|
{ surface: "skill", pattern: "librarian", action: "allow" },
|
|
67
72
|
]);
|
|
68
73
|
});
|
|
69
|
-
});
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
special: { external_directory: "ask" },
|
|
75
|
+
test("invalid action values in object map are ignored", () => {
|
|
76
|
+
const result = normalizeFlatConfig({
|
|
77
|
+
bash: { "git *": "allow", "rm -rf *": "bad" as never },
|
|
75
78
|
});
|
|
76
79
|
expect(result).toEqual([
|
|
77
|
-
{ surface: "
|
|
80
|
+
{ surface: "bash", pattern: "git *", action: "allow" },
|
|
78
81
|
]);
|
|
79
82
|
});
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
describe("
|
|
83
|
-
test("
|
|
84
|
-
const result =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("full ordering: tools → bash → mcp → skills → special", () => {
|
|
95
|
-
const result = normalizeConfig({
|
|
96
|
-
tools: { read: "allow" },
|
|
97
|
-
bash: { "git *": "allow" },
|
|
98
|
-
mcp: { "exa:*": "allow" },
|
|
99
|
-
skills: { librarian: "allow" },
|
|
100
|
-
special: { external_directory: "ask" },
|
|
85
|
+
describe("mixed surfaces", () => {
|
|
86
|
+
test("full mixed config produces rules in insertion order", () => {
|
|
87
|
+
const result = normalizeFlatConfig({
|
|
88
|
+
"*": "ask",
|
|
89
|
+
read: "allow",
|
|
90
|
+
write: "deny",
|
|
91
|
+
bash: { "*": "ask", "git *": "allow" },
|
|
92
|
+
mcp: { mcp_status: "allow" },
|
|
93
|
+
skill: { "*": "ask" },
|
|
94
|
+
external_directory: "ask",
|
|
101
95
|
});
|
|
102
96
|
expect(result).toEqual([
|
|
97
|
+
{ surface: "*", pattern: "*", action: "ask" },
|
|
103
98
|
{ surface: "read", pattern: "*", action: "allow" },
|
|
99
|
+
{ surface: "write", pattern: "*", action: "deny" },
|
|
100
|
+
{ surface: "bash", pattern: "*", action: "ask" },
|
|
104
101
|
{ surface: "bash", pattern: "git *", action: "allow" },
|
|
105
|
-
{ surface: "mcp", pattern: "
|
|
106
|
-
{ surface: "skill", pattern: "
|
|
107
|
-
{ surface: "
|
|
102
|
+
{ surface: "mcp", pattern: "mcp_status", action: "allow" },
|
|
103
|
+
{ surface: "skill", pattern: "*", action: "ask" },
|
|
104
|
+
{ surface: "external_directory", pattern: "*", action: "ask" },
|
|
108
105
|
]);
|
|
109
106
|
});
|
|
110
107
|
});
|
|
111
108
|
|
|
112
|
-
describe("empty and
|
|
113
|
-
test("empty
|
|
114
|
-
expect(
|
|
109
|
+
describe("empty and edge cases", () => {
|
|
110
|
+
test("empty permission object produces empty ruleset", () => {
|
|
111
|
+
expect(normalizeFlatConfig({})).toEqual([]);
|
|
115
112
|
});
|
|
116
113
|
|
|
117
|
-
test("
|
|
118
|
-
|
|
114
|
+
test("non-object values (null, array) nested in map are skipped", () => {
|
|
115
|
+
const result = normalizeFlatConfig({
|
|
116
|
+
bash: null as never,
|
|
117
|
+
read: "allow",
|
|
118
|
+
});
|
|
119
|
+
expect(result).toEqual([
|
|
120
|
+
{ surface: "read", pattern: "*", action: "allow" },
|
|
121
|
+
]);
|
|
119
122
|
});
|
|
120
123
|
});
|
|
121
124
|
});
|