@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,12 +21,14 @@ describe("canonicalizePath", () => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
test("returns empty string for empty input", () => {
|
|
24
|
-
expect(canonicalizePath("")).toBe("");
|
|
24
|
+
expect(canonicalizePath("", "linux")).toBe("");
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
test("returns realpathSync result when path exists", () => {
|
|
28
28
|
realpathSync.mockReturnValueOnce("/real/projects/app");
|
|
29
|
-
expect(canonicalizePath("/projects/link")).toBe(
|
|
29
|
+
expect(canonicalizePath("/projects/link", "linux")).toBe(
|
|
30
|
+
"/real/projects/app",
|
|
31
|
+
);
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
test("re-appends a non-existent leaf to the canonical parent", () => {
|
|
@@ -35,7 +37,7 @@ describe("canonicalizePath", () => {
|
|
|
35
37
|
throw enoent("/projects/app/new-file.ts");
|
|
36
38
|
})
|
|
37
39
|
.mockReturnValueOnce("/canonical/app");
|
|
38
|
-
expect(canonicalizePath("/projects/app/new-file.ts")).toBe(
|
|
40
|
+
expect(canonicalizePath("/projects/app/new-file.ts", "linux")).toBe(
|
|
39
41
|
"/canonical/app/new-file.ts",
|
|
40
42
|
);
|
|
41
43
|
});
|
|
@@ -52,7 +54,7 @@ describe("canonicalizePath", () => {
|
|
|
52
54
|
throw enoent("/projects/app");
|
|
53
55
|
})
|
|
54
56
|
.mockReturnValueOnce("/canonical/projects");
|
|
55
|
-
expect(canonicalizePath("/projects/app/src/new-file.ts")).toBe(
|
|
57
|
+
expect(canonicalizePath("/projects/app/src/new-file.ts", "linux")).toBe(
|
|
56
58
|
"/canonical/projects/app/src/new-file.ts",
|
|
57
59
|
);
|
|
58
60
|
});
|
|
@@ -61,7 +63,7 @@ describe("canonicalizePath", () => {
|
|
|
61
63
|
realpathSync.mockImplementation(() => {
|
|
62
64
|
throw enoent("");
|
|
63
65
|
});
|
|
64
|
-
expect(canonicalizePath("/nonexistent/path/file.ts")).toBe(
|
|
66
|
+
expect(canonicalizePath("/nonexistent/path/file.ts", "linux")).toBe(
|
|
65
67
|
"/nonexistent/path/file.ts",
|
|
66
68
|
);
|
|
67
69
|
});
|
|
@@ -70,14 +72,18 @@ describe("canonicalizePath", () => {
|
|
|
70
72
|
realpathSync.mockImplementation(() => {
|
|
71
73
|
throw Object.assign(new Error("ELOOP"), { code: "ELOOP" });
|
|
72
74
|
});
|
|
73
|
-
expect(canonicalizePath("/some/looping/path")).toBe(
|
|
75
|
+
expect(canonicalizePath("/some/looping/path", "linux")).toBe(
|
|
76
|
+
"/some/looping/path",
|
|
77
|
+
);
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
test("returns input unchanged on EACCES (permission denied)", () => {
|
|
77
81
|
realpathSync.mockImplementation(() => {
|
|
78
82
|
throw Object.assign(new Error("EACCES"), { code: "EACCES" });
|
|
79
83
|
});
|
|
80
|
-
expect(canonicalizePath("/restricted/path")).toBe(
|
|
84
|
+
expect(canonicalizePath("/restricted/path", "linux")).toBe(
|
|
85
|
+
"/restricted/path",
|
|
86
|
+
);
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
test("handles ENOTDIR by walking up (like ENOENT)", () => {
|
|
@@ -86,8 +92,28 @@ describe("canonicalizePath", () => {
|
|
|
86
92
|
throw Object.assign(new Error("ENOTDIR"), { code: "ENOTDIR" });
|
|
87
93
|
})
|
|
88
94
|
.mockReturnValueOnce("/real/parent");
|
|
89
|
-
expect(canonicalizePath("/real/parent/not-a-dir")).toBe(
|
|
95
|
+
expect(canonicalizePath("/real/parent/not-a-dir", "linux")).toBe(
|
|
90
96
|
"/real/parent/not-a-dir",
|
|
91
97
|
);
|
|
92
98
|
});
|
|
99
|
+
|
|
100
|
+
// ── injected platform flavor (win32-separator splitting) ──────────────
|
|
101
|
+
|
|
102
|
+
test("win32: splits and rejoins on the backslash separator", () => {
|
|
103
|
+
realpathSync
|
|
104
|
+
.mockImplementationOnce(() => {
|
|
105
|
+
throw enoent("C:\\projects\\link\\file.ts");
|
|
106
|
+
})
|
|
107
|
+
.mockReturnValueOnce("C:\\real\\app");
|
|
108
|
+
expect(canonicalizePath("C:\\projects\\link\\file.ts", "win32")).toBe(
|
|
109
|
+
"C:\\real\\app\\file.ts",
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("win32: resolves an existing path via realpathSync", () => {
|
|
114
|
+
realpathSync.mockReturnValueOnce("C:\\real\\app");
|
|
115
|
+
expect(canonicalizePath("C:\\projects\\link", "win32")).toBe(
|
|
116
|
+
"C:\\real\\app",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
93
119
|
});
|
|
@@ -30,7 +30,11 @@ function makeForwarder() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function makeManager() {
|
|
33
|
-
return new ForwardingManager(
|
|
33
|
+
return new ForwardingManager(
|
|
34
|
+
"/agent/subagent-sessions",
|
|
35
|
+
makeForwarder(),
|
|
36
|
+
"linux",
|
|
37
|
+
);
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
@@ -180,6 +184,7 @@ describe("ForwardingManager", () => {
|
|
|
180
184
|
const manager = new ForwardingManager(
|
|
181
185
|
"/custom/subagent-dir",
|
|
182
186
|
makeForwarder(),
|
|
187
|
+
"linux",
|
|
183
188
|
);
|
|
184
189
|
const ctx = makeCtx();
|
|
185
190
|
manager.start(ctx);
|
|
@@ -187,6 +192,7 @@ describe("ForwardingManager", () => {
|
|
|
187
192
|
expect(mockIsSubagentExecutionContext).toHaveBeenCalledWith(
|
|
188
193
|
ctx,
|
|
189
194
|
"/custom/subagent-dir",
|
|
195
|
+
"linux",
|
|
190
196
|
undefined,
|
|
191
197
|
);
|
|
192
198
|
});
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "#src/handlers/gates/descriptor";
|
|
23
23
|
import { describeExternalDirectoryGate } from "#src/handlers/gates/external-directory";
|
|
24
24
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
25
|
+
import { PathNormalizer } from "#src/path-normalizer";
|
|
25
26
|
import { PermissionResolver } from "#src/permission-resolver";
|
|
26
27
|
import { SessionRules } from "#src/session-rules";
|
|
27
28
|
import type { ScopeConfig } from "#src/types";
|
|
@@ -83,7 +84,13 @@ describe("external_directory symlink acceptance (#418)", () => {
|
|
|
83
84
|
},
|
|
84
85
|
});
|
|
85
86
|
try {
|
|
86
|
-
const result = describeExternalDirectoryGate(
|
|
87
|
+
const result = describeExternalDirectoryGate(
|
|
88
|
+
readTcc(),
|
|
89
|
+
[],
|
|
90
|
+
resolver,
|
|
91
|
+
new PathNormalizer(process.platform, cwd),
|
|
92
|
+
"linux",
|
|
93
|
+
);
|
|
87
94
|
expect(isGateDescriptor(result)).toBe(true);
|
|
88
95
|
expect((result as GateDescriptor).preCheck?.state).toBe("allow");
|
|
89
96
|
} finally {
|
|
@@ -101,7 +108,13 @@ describe("external_directory symlink acceptance (#418)", () => {
|
|
|
101
108
|
},
|
|
102
109
|
});
|
|
103
110
|
try {
|
|
104
|
-
const result = describeExternalDirectoryGate(
|
|
111
|
+
const result = describeExternalDirectoryGate(
|
|
112
|
+
readTcc(),
|
|
113
|
+
[],
|
|
114
|
+
resolver,
|
|
115
|
+
new PathNormalizer(process.platform, cwd),
|
|
116
|
+
"linux",
|
|
117
|
+
);
|
|
105
118
|
expect(isGateDescriptor(result)).toBe(true);
|
|
106
119
|
expect((result as GateDescriptor).preCheck?.state).toBe("allow");
|
|
107
120
|
} finally {
|
|
@@ -114,7 +127,13 @@ describe("external_directory symlink acceptance (#418)", () => {
|
|
|
114
127
|
permission: { external_directory: { "*": "ask" } },
|
|
115
128
|
});
|
|
116
129
|
try {
|
|
117
|
-
const result = describeExternalDirectoryGate(
|
|
130
|
+
const result = describeExternalDirectoryGate(
|
|
131
|
+
readTcc(),
|
|
132
|
+
[],
|
|
133
|
+
resolver,
|
|
134
|
+
new PathNormalizer(process.platform, cwd),
|
|
135
|
+
"linux",
|
|
136
|
+
);
|
|
118
137
|
expect(isGateDescriptor(result)).toBe(true);
|
|
119
138
|
expect((result as GateDescriptor).preCheck?.state).toBe("ask");
|
|
120
139
|
} finally {
|
|
@@ -137,7 +156,10 @@ describe("external_directory symlink acceptance (#418)", () => {
|
|
|
137
156
|
toolCallId: "tc-2",
|
|
138
157
|
cwd,
|
|
139
158
|
};
|
|
140
|
-
const program = await BashProgram.parse(
|
|
159
|
+
const program = await BashProgram.parse(
|
|
160
|
+
command,
|
|
161
|
+
new PathNormalizer(process.platform, cwd),
|
|
162
|
+
);
|
|
141
163
|
const result = describeBashExternalDirectoryGate(tcc, program, resolver);
|
|
142
164
|
// All external paths are covered by the allow → bypass, no prompt.
|
|
143
165
|
expect(isGateBypass(result)).toBe(true);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { describe, expect, it } from "vitest";
|
|
13
13
|
import { BashProgram } from "#src/access-intent/bash/program";
|
|
14
14
|
import { resolveBashCommandCheck } from "#src/handlers/gates/bash-command";
|
|
15
|
+
import { PathNormalizer } from "#src/path-normalizer";
|
|
15
16
|
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
16
17
|
import type { PermissionState } from "#src/types";
|
|
17
18
|
|
|
@@ -48,7 +49,10 @@ async function decide(
|
|
|
48
49
|
command: string,
|
|
49
50
|
resolver: ScopedPermissionResolver,
|
|
50
51
|
): Promise<PermissionState> {
|
|
51
|
-
const program = await BashProgram.parse(
|
|
52
|
+
const program = await BashProgram.parse(
|
|
53
|
+
command,
|
|
54
|
+
new PathNormalizer(process.platform, "/cwd"),
|
|
55
|
+
);
|
|
52
56
|
return resolveBashCommandCheck(
|
|
53
57
|
command,
|
|
54
58
|
program.commands(),
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from "#src/handlers/gates/descriptor";
|
|
10
10
|
import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
|
|
11
11
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
12
|
+
import { PathNormalizer } from "#src/path-normalizer";
|
|
12
13
|
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
13
14
|
import type { PermissionCheckResult } from "#src/types";
|
|
14
15
|
import { getNonEmptyString, toRecord } from "#src/value-guards";
|
|
@@ -44,7 +45,6 @@ function makeCheckResult(
|
|
|
44
45
|
/** Extract the policy match values a resolve(intent) call carries. */
|
|
45
46
|
function intentValues(intent: AccessIntent): readonly string[] {
|
|
46
47
|
if (intent.kind === "access-path") return intent.path.matchValues();
|
|
47
|
-
if (intent.kind === "path-values") return intent.values;
|
|
48
48
|
return [];
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -60,7 +60,10 @@ async function describeGate(
|
|
|
60
60
|
const command = getNonEmptyString(toRecord(tcc.input).command);
|
|
61
61
|
const bashProgram =
|
|
62
62
|
tcc.toolName === "bash" && command
|
|
63
|
-
? await BashProgram.parse(
|
|
63
|
+
? await BashProgram.parse(
|
|
64
|
+
command,
|
|
65
|
+
new PathNormalizer(process.platform, tcc.cwd),
|
|
66
|
+
)
|
|
64
67
|
: null;
|
|
65
68
|
return describeBashExternalDirectoryGate(tcc, bashProgram, resolver);
|
|
66
69
|
}
|
|
@@ -9,6 +9,7 @@ vi.mock("node:os", () => {
|
|
|
9
9
|
};
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
import { AccessPath } from "#src/access-intent/access-path";
|
|
12
13
|
import { BashProgram } from "#src/access-intent/bash/program";
|
|
13
14
|
import { describeBashPathGate } from "#src/handlers/gates/bash-path";
|
|
14
15
|
import type {
|
|
@@ -18,6 +19,7 @@ import type {
|
|
|
18
19
|
} from "#src/handlers/gates/descriptor";
|
|
19
20
|
import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
|
|
20
21
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
22
|
+
import { PathNormalizer } from "#src/path-normalizer";
|
|
21
23
|
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
22
24
|
import { getNonEmptyString, toRecord } from "#src/value-guards";
|
|
23
25
|
|
|
@@ -44,7 +46,10 @@ async function describeGate(
|
|
|
44
46
|
const command = getNonEmptyString(toRecord(tcc.input).command);
|
|
45
47
|
const bashProgram =
|
|
46
48
|
tcc.toolName === "bash" && command
|
|
47
|
-
? await BashProgram.parse(
|
|
49
|
+
? await BashProgram.parse(
|
|
50
|
+
command,
|
|
51
|
+
new PathNormalizer(process.platform, tcc.cwd),
|
|
52
|
+
)
|
|
48
53
|
: null;
|
|
49
54
|
return describeBashPathGate(tcc, bashProgram, resolver);
|
|
50
55
|
}
|
|
@@ -229,13 +234,13 @@ describe("describeBashPathGate", () => {
|
|
|
229
234
|
)) as GateDescriptor;
|
|
230
235
|
|
|
231
236
|
expect(resolver.resolve).toHaveBeenCalledWith({
|
|
232
|
-
kind: "path
|
|
237
|
+
kind: "access-path",
|
|
233
238
|
surface: "path",
|
|
234
|
-
|
|
235
|
-
"/test/project
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
|
|
239
|
+
path: AccessPath.forPath("src/file.txt", {
|
|
240
|
+
cwd: "/test/project",
|
|
241
|
+
resolveBase: "/test/project/nested",
|
|
242
|
+
platform: "linux",
|
|
243
|
+
}),
|
|
239
244
|
agentName: undefined,
|
|
240
245
|
});
|
|
241
246
|
// The raw token drives the prompt, denial context, and session approval.
|
|
@@ -256,9 +261,9 @@ describe("describeBashPathGate", () => {
|
|
|
256
261
|
);
|
|
257
262
|
|
|
258
263
|
expect(resolver.resolve).toHaveBeenCalledWith({
|
|
259
|
-
kind: "path
|
|
264
|
+
kind: "access-path",
|
|
260
265
|
surface: "path",
|
|
261
|
-
|
|
266
|
+
path: AccessPath.forLiteral("src/foo.ts"),
|
|
262
267
|
agentName: undefined,
|
|
263
268
|
});
|
|
264
269
|
});
|
|
@@ -25,7 +25,10 @@ function makeCheckResult(
|
|
|
25
25
|
|
|
26
26
|
describe("resolveExternalDirectoryPolicy", () => {
|
|
27
27
|
it("resolves the path's match aliases on the external_directory surface (#418)", () => {
|
|
28
|
-
const path = AccessPath.
|
|
28
|
+
const path = AccessPath.forPath("/outside/a.ts", {
|
|
29
|
+
cwd,
|
|
30
|
+
platform: "linux",
|
|
31
|
+
});
|
|
29
32
|
const resolver = makeResolver(makeCheckResult("ask"));
|
|
30
33
|
|
|
31
34
|
const result = resolveExternalDirectoryPolicy(path, resolver, undefined);
|
|
@@ -40,7 +43,10 @@ describe("resolveExternalDirectoryPolicy", () => {
|
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
it("threads the agent name through to the resolver", () => {
|
|
43
|
-
const path = AccessPath.
|
|
46
|
+
const path = AccessPath.forPath("/outside/a.ts", {
|
|
47
|
+
cwd,
|
|
48
|
+
platform: "linux",
|
|
49
|
+
});
|
|
44
50
|
const resolver = makeResolver(makeCheckResult("allow"));
|
|
45
51
|
|
|
46
52
|
resolveExternalDirectoryPolicy(path, resolver, "reviewer");
|
|
@@ -57,8 +63,8 @@ describe("resolveExternalDirectoryPolicy", () => {
|
|
|
57
63
|
describe("selectUncoveredExternalPaths", () => {
|
|
58
64
|
it("returns no uncovered paths when every path resolves to allow", () => {
|
|
59
65
|
const paths = [
|
|
60
|
-
AccessPath.
|
|
61
|
-
AccessPath.
|
|
66
|
+
AccessPath.forPath("/outside/a.ts", { cwd, platform: "linux" }),
|
|
67
|
+
AccessPath.forPath("/outside/b.ts", { cwd, platform: "linux" }),
|
|
62
68
|
];
|
|
63
69
|
const resolver = makeResolver(makeCheckResult("allow"));
|
|
64
70
|
|
|
@@ -73,16 +79,18 @@ describe("selectUncoveredExternalPaths", () => {
|
|
|
73
79
|
});
|
|
74
80
|
|
|
75
81
|
it("collects only paths whose resolved state is not allow", () => {
|
|
76
|
-
const allowed = AccessPath.
|
|
77
|
-
|
|
82
|
+
const allowed = AccessPath.forPath("/outside/ok.ts", {
|
|
83
|
+
cwd,
|
|
84
|
+
platform: "linux",
|
|
85
|
+
});
|
|
86
|
+
const asked = AccessPath.forPath("/outside/ask.ts", {
|
|
87
|
+
cwd,
|
|
88
|
+
platform: "linux",
|
|
89
|
+
});
|
|
78
90
|
const resolver = makeResolver();
|
|
79
91
|
resolver.resolve.mockImplementation((intent) => {
|
|
80
92
|
const values =
|
|
81
|
-
intent.kind === "access-path"
|
|
82
|
-
? intent.path.matchValues()
|
|
83
|
-
: intent.kind === "path-values"
|
|
84
|
-
? intent.values
|
|
85
|
-
: [];
|
|
93
|
+
intent.kind === "access-path" ? intent.path.matchValues() : [];
|
|
86
94
|
return values.includes("/outside/ok.ts")
|
|
87
95
|
? makeCheckResult("allow")
|
|
88
96
|
: makeCheckResult("ask");
|
|
@@ -98,16 +106,18 @@ describe("selectUncoveredExternalPaths", () => {
|
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
it("returns the most restrictive uncovered check as worstCheck (deny > ask)", () => {
|
|
101
|
-
const asked = AccessPath.
|
|
102
|
-
|
|
109
|
+
const asked = AccessPath.forPath("/outside/ask.ts", {
|
|
110
|
+
cwd,
|
|
111
|
+
platform: "linux",
|
|
112
|
+
});
|
|
113
|
+
const denied = AccessPath.forPath("/outside/deny.ts", {
|
|
114
|
+
cwd,
|
|
115
|
+
platform: "linux",
|
|
116
|
+
});
|
|
103
117
|
const resolver = makeResolver();
|
|
104
118
|
resolver.resolve.mockImplementation((intent) => {
|
|
105
119
|
const values =
|
|
106
|
-
intent.kind === "access-path"
|
|
107
|
-
? intent.path.matchValues()
|
|
108
|
-
: intent.kind === "path-values"
|
|
109
|
-
? intent.values
|
|
110
|
-
: [];
|
|
120
|
+
intent.kind === "access-path" ? intent.path.matchValues() : [];
|
|
111
121
|
return values.includes("/outside/deny.ts")
|
|
112
122
|
? makeCheckResult("deny")
|
|
113
123
|
: makeCheckResult("ask");
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
|
|
8
8
|
import { describeExternalDirectoryGate } from "#src/handlers/gates/external-directory";
|
|
9
9
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
10
|
+
import { PathNormalizer } from "#src/path-normalizer";
|
|
10
11
|
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
11
12
|
import type { ToolAccessExtractorLookup } from "#src/tool-access-extractor-registry";
|
|
12
13
|
import { makeResolver } from "#test/helpers/gate-fixtures";
|
|
@@ -37,7 +38,14 @@ function gateUnderTest(
|
|
|
37
38
|
makeCheckResult({ state: "ask", toolName: "external_directory" }),
|
|
38
39
|
),
|
|
39
40
|
) {
|
|
40
|
-
return describeExternalDirectoryGate(
|
|
41
|
+
return describeExternalDirectoryGate(
|
|
42
|
+
tcc,
|
|
43
|
+
infraDirs,
|
|
44
|
+
resolver,
|
|
45
|
+
new PathNormalizer(process.platform, tcc.cwd),
|
|
46
|
+
"linux",
|
|
47
|
+
extractors,
|
|
48
|
+
);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
// ── tests ────────────────────��────────────────────────────────────��────────
|