@gotgenes/pi-permission-system 16.2.1 → 17.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.
- package/CHANGELOG.md +26 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/access-intent/access-intent.ts +8 -10
- package/src/access-intent/access-path.ts +39 -16
- package/src/access-intent/bash/bash-path-resolver.ts +531 -0
- package/src/access-intent/bash/program.ts +21 -14
- package/src/access-intent/bash/token-classification.ts +1 -1
- package/src/canonicalize-path.ts +11 -5
- package/src/forwarded-permissions/permission-forwarder.ts +11 -1
- package/src/forwarding-manager.ts +7 -1
- package/src/handlers/before-agent-start.ts +1 -0
- package/src/handlers/gates/bash-path-extractor.ts +7 -5
- package/src/handlers/gates/bash-path.ts +13 -13
- package/src/handlers/gates/external-directory.ts +14 -16
- package/src/handlers/gates/path.ts +12 -7
- package/src/handlers/gates/skill-read.ts +7 -1
- package/src/handlers/gates/tool-call-gate-pipeline.ts +21 -4
- package/src/handlers/gates/tool.ts +4 -2
- package/src/index.ts +12 -1
- package/src/input-normalizer.ts +9 -4
- package/src/path-normalizer.ts +70 -0
- package/src/path-utils.ts +39 -28
- package/src/permission-manager.ts +21 -7
- package/src/permission-session.ts +35 -2
- package/src/prompting-gateway.ts +3 -0
- package/src/rule.ts +8 -6
- package/src/skill-prompt-sanitizer.ts +15 -4
- package/src/subagent-context.ts +17 -9
- package/test/access-intent/access-path.test.ts +124 -15
- package/test/access-intent/bash/program.test.ts +178 -80
- package/test/bash-external-directory.test.ts +15 -1
- package/test/canonicalize-path.test.ts +34 -8
- package/test/forwarding-manager.test.ts +7 -1
- package/test/handlers/external-directory-symlink-acceptance.test.ts +26 -4
- package/test/handlers/gates/bash-command-metamorphic.test.ts +5 -1
- package/test/handlers/gates/bash-external-directory.test.ts +5 -2
- package/test/handlers/gates/bash-path.test.ts +14 -9
- package/test/handlers/gates/external-directory-policy.test.ts +28 -18
- package/test/handlers/gates/external-directory.test.ts +9 -1
- package/test/handlers/gates/path.test.ts +90 -20
- package/test/handlers/gates/skill-read.test.ts +22 -13
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +2 -1
- package/test/handlers/gates/tool.test.ts +13 -1
- package/test/helpers/gate-fixtures.ts +11 -2
- package/test/helpers/session-fixtures.ts +3 -0
- package/test/input-normalizer.test.ts +104 -39
- package/test/path-normalizer.test.ts +123 -0
- package/test/path-utils.test.ts +130 -51
- package/test/permission-forwarder.test.ts +1 -0
- package/test/permission-manager-unified.test.ts +40 -0
- package/test/permission-resolver.test.ts +14 -32
- package/test/permission-session.test.ts +41 -0
- package/test/pi-infrastructure-read.test.ts +27 -4
- package/test/prompting-gateway.test.ts +1 -0
- package/test/rule.test.ts +88 -42
- package/test/session-rules.test.ts +21 -4
- package/test/skill-prompt-sanitizer.test.ts +69 -14
- package/test/subagent-context.test.ts +77 -31
- package/test/synthesize.test.ts +13 -11
- package/src/access-intent/bash/cwd-projection.ts +0 -498
|
@@ -21,7 +21,7 @@ vi.mock("node:fs", () => ({
|
|
|
21
21
|
|
|
22
22
|
import { AccessPath } from "#src/access-intent/access-path";
|
|
23
23
|
|
|
24
|
-
describe("AccessPath.
|
|
24
|
+
describe("AccessPath.forPath", () => {
|
|
25
25
|
const cwd = "/projects/my-app";
|
|
26
26
|
|
|
27
27
|
beforeEach(() => {
|
|
@@ -36,19 +36,25 @@ describe("AccessPath.forExternalDirectory", () => {
|
|
|
36
36
|
p.startsWith("/tmp") ? `/private${p}` : p,
|
|
37
37
|
);
|
|
38
38
|
expect(
|
|
39
|
-
AccessPath.
|
|
39
|
+
AccessPath.forPath("/tmp/x", { cwd, platform: "linux" }).matchValues(),
|
|
40
40
|
).toEqual(["/tmp/x", "/private/tmp/x"]);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test("deduplicates when the canonical form equals the lexical form", () => {
|
|
44
44
|
expect(
|
|
45
|
-
AccessPath.
|
|
45
|
+
AccessPath.forPath("/etc/hosts", {
|
|
46
|
+
cwd,
|
|
47
|
+
platform: "linux",
|
|
48
|
+
}).matchValues(),
|
|
46
49
|
).toEqual(["/etc/hosts"]);
|
|
47
50
|
});
|
|
48
51
|
|
|
49
52
|
test("keeps the relative aliases for an in-cwd token without duplicating", () => {
|
|
50
53
|
expect(
|
|
51
|
-
AccessPath.
|
|
54
|
+
AccessPath.forPath("src/foo.ts", {
|
|
55
|
+
cwd,
|
|
56
|
+
platform: "linux",
|
|
57
|
+
}).matchValues(),
|
|
52
58
|
).toEqual(["/projects/my-app/src/foo.ts", "src/foo.ts"]);
|
|
53
59
|
});
|
|
54
60
|
|
|
@@ -56,9 +62,68 @@ describe("AccessPath.forExternalDirectory", () => {
|
|
|
56
62
|
// Force canonicalizePath to return the original (no-op symlink resolution
|
|
57
63
|
// effectively means canonical === lexical, handled by dedup).
|
|
58
64
|
expect(
|
|
59
|
-
AccessPath.
|
|
65
|
+
AccessPath.forPath("/etc/hosts", {
|
|
66
|
+
cwd,
|
|
67
|
+
platform: "linux",
|
|
68
|
+
}).matchValues(),
|
|
60
69
|
).not.toHaveLength(0);
|
|
61
70
|
});
|
|
71
|
+
|
|
72
|
+
test("resolves a relative token against an explicit resolveBase", () => {
|
|
73
|
+
// The cd-folded effective base differs from cwd (the bash-path case).
|
|
74
|
+
expect(
|
|
75
|
+
AccessPath.forPath("foo.ts", {
|
|
76
|
+
cwd,
|
|
77
|
+
resolveBase: "/projects/my-app/sub",
|
|
78
|
+
platform: "linux",
|
|
79
|
+
}).matchValues(),
|
|
80
|
+
).toEqual(["/projects/my-app/sub/foo.ts", "sub/foo.ts", "foo.ts"]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("adds the canonical alias resolved against resolveBase", () => {
|
|
84
|
+
realpathSync.mockImplementation((p: string) =>
|
|
85
|
+
p === "/projects/my-app/sub/foo.ts" ? "/real/foo.ts" : p,
|
|
86
|
+
);
|
|
87
|
+
expect(
|
|
88
|
+
AccessPath.forPath("foo.ts", {
|
|
89
|
+
cwd,
|
|
90
|
+
resolveBase: "/projects/my-app/sub",
|
|
91
|
+
platform: "linux",
|
|
92
|
+
}).matchValues(),
|
|
93
|
+
).toEqual([
|
|
94
|
+
"/projects/my-app/sub/foo.ts",
|
|
95
|
+
"sub/foo.ts",
|
|
96
|
+
"foo.ts",
|
|
97
|
+
"/real/foo.ts",
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("platform option", () => {
|
|
103
|
+
test("win32: builds lexical/match/boundary values with win32 rules", () => {
|
|
104
|
+
const ap = AccessPath.forPath("src\\foo.ts", {
|
|
105
|
+
cwd: "C:\\Projects\\App",
|
|
106
|
+
platform: "win32",
|
|
107
|
+
});
|
|
108
|
+
expect(ap.value()).toBe("c:\\projects\\app\\src\\foo.ts");
|
|
109
|
+
expect(ap.boundaryValue()).toBe("c:\\projects\\app\\src\\foo.ts");
|
|
110
|
+
expect(ap.matchValues()).toEqual([
|
|
111
|
+
"c:\\projects\\app\\src\\foo.ts",
|
|
112
|
+
"src\\foo.ts",
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("win32: lowercases the symlink-resolved boundary value", () => {
|
|
117
|
+
realpathSync.mockImplementation((p: string) =>
|
|
118
|
+
p === "c:\\projects\\app\\link" ? "C:\\Real\\App" : p,
|
|
119
|
+
);
|
|
120
|
+
expect(
|
|
121
|
+
AccessPath.forPath("link", {
|
|
122
|
+
cwd: "C:\\Projects\\App",
|
|
123
|
+
platform: "win32",
|
|
124
|
+
}).boundaryValue(),
|
|
125
|
+
).toBe("c:\\real\\app");
|
|
126
|
+
});
|
|
62
127
|
});
|
|
63
128
|
|
|
64
129
|
describe("boundaryValue()", () => {
|
|
@@ -67,18 +132,26 @@ describe("AccessPath.forExternalDirectory", () => {
|
|
|
67
132
|
p.startsWith("/tmp") ? `/private${p}` : p,
|
|
68
133
|
);
|
|
69
134
|
expect(
|
|
70
|
-
AccessPath.
|
|
135
|
+
AccessPath.forPath("/tmp/x", {
|
|
136
|
+
cwd,
|
|
137
|
+
platform: "linux",
|
|
138
|
+
}).boundaryValue(),
|
|
71
139
|
).toBe("/private/tmp/x");
|
|
72
140
|
});
|
|
73
141
|
|
|
74
142
|
test("returns the lexical form when path has no symlinks", () => {
|
|
75
143
|
expect(
|
|
76
|
-
AccessPath.
|
|
144
|
+
AccessPath.forPath("/etc/hosts", {
|
|
145
|
+
cwd,
|
|
146
|
+
platform: "linux",
|
|
147
|
+
}).boundaryValue(),
|
|
77
148
|
).toBe("/etc/hosts");
|
|
78
149
|
});
|
|
79
150
|
|
|
80
151
|
test("returns empty string for empty input", () => {
|
|
81
|
-
expect(
|
|
152
|
+
expect(
|
|
153
|
+
AccessPath.forPath("", { cwd, platform: "linux" }).boundaryValue(),
|
|
154
|
+
).toBe("");
|
|
82
155
|
});
|
|
83
156
|
});
|
|
84
157
|
|
|
@@ -88,20 +161,56 @@ describe("AccessPath.forExternalDirectory", () => {
|
|
|
88
161
|
p.startsWith("/tmp") ? `/private${p}` : p,
|
|
89
162
|
);
|
|
90
163
|
// Even when the path resolves to a different canonical, value() stays lexical.
|
|
91
|
-
expect(
|
|
92
|
-
"/tmp/x",
|
|
93
|
-
);
|
|
164
|
+
expect(
|
|
165
|
+
AccessPath.forPath("/tmp/x", { cwd, platform: "linux" }).value(),
|
|
166
|
+
).toBe("/tmp/x");
|
|
94
167
|
});
|
|
95
168
|
|
|
96
169
|
test("normalizes the path against cwd", () => {
|
|
97
170
|
// A relative path becomes an absolute lexical value.
|
|
98
|
-
expect(
|
|
99
|
-
"
|
|
100
|
-
);
|
|
171
|
+
expect(
|
|
172
|
+
AccessPath.forPath("src/foo.ts", { cwd, platform: "linux" }).value(),
|
|
173
|
+
).toBe("/projects/my-app/src/foo.ts");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("normalizes a relative path against an explicit resolveBase", () => {
|
|
177
|
+
expect(
|
|
178
|
+
AccessPath.forPath("foo.ts", {
|
|
179
|
+
cwd,
|
|
180
|
+
resolveBase: "/projects/my-app/sub",
|
|
181
|
+
platform: "linux",
|
|
182
|
+
}).value(),
|
|
183
|
+
).toBe("/projects/my-app/sub/foo.ts");
|
|
101
184
|
});
|
|
102
185
|
|
|
103
186
|
test("returns empty string for empty input", () => {
|
|
104
|
-
expect(AccessPath.
|
|
187
|
+
expect(AccessPath.forPath("", { cwd, platform: "linux" }).value()).toBe(
|
|
188
|
+
"",
|
|
189
|
+
);
|
|
105
190
|
});
|
|
106
191
|
});
|
|
107
192
|
});
|
|
193
|
+
|
|
194
|
+
describe("AccessPath.forLiteral", () => {
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
realpathSync.mockReset();
|
|
197
|
+
realpathSync.mockImplementation((p: string) => p);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("matchValues() carries only the literal — no canonical, no absolute", () => {
|
|
201
|
+
expect(AccessPath.forLiteral("foo.ts").matchValues()).toEqual(["foo.ts"]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("boundaryValue() is empty (no outside-cwd notion for an unknown base)", () => {
|
|
205
|
+
expect(AccessPath.forLiteral("foo.ts").boundaryValue()).toBe("");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("value() returns the literal", () => {
|
|
209
|
+
expect(AccessPath.forLiteral("foo.ts").value()).toBe("foo.ts");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("an empty literal yields no match values", () => {
|
|
213
|
+
expect(AccessPath.forLiteral("").matchValues()).toEqual([]);
|
|
214
|
+
expect(AccessPath.forLiteral("").value()).toBe("");
|
|
215
|
+
});
|
|
216
|
+
});
|