@gotgenes/pi-permission-system 8.1.0 → 8.2.1
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 +15 -0
- package/package.json +1 -1
- package/src/config-loader.ts +53 -46
- package/src/handlers/gates/bash-external-directory.ts +2 -4
- package/src/handlers/gates/bash-path-extractor.ts +135 -169
- package/src/handlers/gates/bash-path.ts +2 -4
- package/src/handlers/gates/bash-token-classification.ts +105 -0
- package/src/handlers/gates/descriptor.ts +6 -6
- package/src/handlers/gates/external-directory.ts +2 -4
- package/src/handlers/gates/helpers.ts +30 -1
- package/src/handlers/gates/path.ts +2 -4
- package/src/handlers/gates/runner.ts +29 -56
- package/src/handlers/gates/tool.ts +5 -4
- package/src/handlers/permission-gate-handler.ts +4 -3
- package/src/permission-manager.ts +6 -49
- package/src/permission-session.ts +3 -2
- package/src/scope-merge.ts +72 -0
- package/src/session-approval.ts +43 -0
- package/src/session-rules.ts +13 -0
- package/test/config-loader.test.ts +82 -0
- package/test/handlers/before-agent-start.test.ts +2 -20
- package/test/handlers/external-directory-integration.test.ts +44 -82
- package/test/handlers/external-directory-session-dedup.test.ts +17 -41
- package/test/handlers/gates/bash-external-directory.test.ts +11 -9
- package/test/handlers/gates/bash-path.test.ts +5 -26
- package/test/handlers/gates/bash-token-classification.test.ts +241 -0
- package/test/handlers/gates/external-directory.test.ts +2 -5
- package/test/handlers/gates/helpers.test.ts +81 -0
- package/test/handlers/gates/path.test.ts +5 -14
- package/test/handlers/gates/runner.test.ts +95 -113
- package/test/handlers/gates/tool.test.ts +2 -2
- package/test/handlers/input-events.test.ts +42 -95
- package/test/handlers/input.test.ts +3 -71
- package/test/handlers/lifecycle.test.ts +3 -20
- package/test/handlers/tool-call-events.test.ts +30 -127
- package/test/handlers/tool-call.test.ts +21 -110
- package/test/helpers/gate-fixtures.ts +105 -0
- package/test/helpers/handler-fixtures.ts +141 -0
- package/test/helpers/manager-harness.ts +51 -0
- package/test/permission-session.test.ts +7 -22
- package/test/permission-system.test.ts +4 -40
- package/test/scope-merge.test.ts +116 -0
- package/test/session-approval.test.ts +75 -0
- package/test/session-rules.test.ts +49 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-backed PermissionManager harness for integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Writes a real config file and agents directory to a temp directory so
|
|
5
|
+
* PermissionManager can load them without mocking the file system.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { PermissionManager } from "#src/permission-manager";
|
|
12
|
+
import type { ScopeConfig } from "#src/types";
|
|
13
|
+
|
|
14
|
+
export type CreateManagerOptions = {
|
|
15
|
+
mcpServerNames?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function createManager(
|
|
19
|
+
config: ScopeConfig,
|
|
20
|
+
agentFiles: Record<string, string> = {},
|
|
21
|
+
options: CreateManagerOptions = {},
|
|
22
|
+
) {
|
|
23
|
+
const baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-test-"));
|
|
24
|
+
const globalConfigPath = join(baseDir, "pi-permissions.jsonc");
|
|
25
|
+
const agentsDir = join(baseDir, "agents");
|
|
26
|
+
|
|
27
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
28
|
+
writeFileSync(
|
|
29
|
+
globalConfigPath,
|
|
30
|
+
`${JSON.stringify(config, null, 2)}\n`,
|
|
31
|
+
"utf8",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
for (const [name, content] of Object.entries(agentFiles)) {
|
|
35
|
+
writeFileSync(join(agentsDir, `${name}.md`), content, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const manager = new PermissionManager({
|
|
39
|
+
globalConfigPath,
|
|
40
|
+
agentsDir,
|
|
41
|
+
mcpServerNames: options.mcpServerNames,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
manager,
|
|
46
|
+
globalConfigPath,
|
|
47
|
+
cleanup: (): void => {
|
|
48
|
+
rmSync(baseDir, { recursive: true, force: true });
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -36,8 +36,10 @@ import {
|
|
|
36
36
|
PermissionSession,
|
|
37
37
|
type PermissionSessionRuntimeDeps,
|
|
38
38
|
} from "#src/permission-session";
|
|
39
|
+
import { SessionApproval } from "#src/session-approval";
|
|
39
40
|
import type { SessionLogger } from "#src/session-logger";
|
|
40
41
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
42
|
+
import { makeCtx } from "#test/helpers/handler-fixtures";
|
|
41
43
|
|
|
42
44
|
function makeSkillEntry(
|
|
43
45
|
name: string,
|
|
@@ -93,25 +95,6 @@ function makeForwarding(): ForwardingController {
|
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
97
|
-
return {
|
|
98
|
-
cwd: "/test/project",
|
|
99
|
-
hasUI: true,
|
|
100
|
-
ui: {
|
|
101
|
-
setStatus: vi.fn(),
|
|
102
|
-
notify: vi.fn(),
|
|
103
|
-
select: vi.fn(),
|
|
104
|
-
input: vi.fn(),
|
|
105
|
-
},
|
|
106
|
-
sessionManager: {
|
|
107
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
108
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
109
|
-
addEntry: vi.fn(),
|
|
110
|
-
},
|
|
111
|
-
...overrides,
|
|
112
|
-
} as unknown as ExtensionContext;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
98
|
function makePermissionManager(
|
|
116
99
|
overrides: Partial<PermissionManager> = {},
|
|
117
100
|
): PermissionManager {
|
|
@@ -219,9 +202,11 @@ describe("PermissionSession", () => {
|
|
|
219
202
|
expect(rules).toEqual([]);
|
|
220
203
|
});
|
|
221
204
|
|
|
222
|
-
it("delegates
|
|
205
|
+
it("delegates recordSessionApproval to internal SessionRules", () => {
|
|
223
206
|
const { session } = createSession();
|
|
224
|
-
session.
|
|
207
|
+
session.recordSessionApproval(
|
|
208
|
+
SessionApproval.single("bash", "/usr/bin/*"),
|
|
209
|
+
);
|
|
225
210
|
const rules = session.getSessionRuleset();
|
|
226
211
|
expect(rules).toHaveLength(1);
|
|
227
212
|
expect(rules[0]).toMatchObject({
|
|
@@ -325,7 +310,7 @@ describe("PermissionSession", () => {
|
|
|
325
310
|
describe("shutdown", () => {
|
|
326
311
|
it("clears session rules", () => {
|
|
327
312
|
const { session } = createSession();
|
|
328
|
-
session.
|
|
313
|
+
session.recordSessionApproval(SessionApproval.single("bash", "*"));
|
|
329
314
|
expect(session.getSessionRuleset()).toHaveLength(1);
|
|
330
315
|
|
|
331
316
|
session.shutdown();
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { dirname, join, resolve } from "node:path";
|
|
11
11
|
import { expect, test } from "vitest";
|
|
12
|
-
|
|
13
12
|
import {
|
|
14
13
|
createActiveToolsCacheKey,
|
|
15
14
|
createBeforeAgentStartPromptStateKey,
|
|
@@ -47,45 +46,10 @@ import {
|
|
|
47
46
|
canResolveAskPermissionRequest,
|
|
48
47
|
shouldAutoApprovePermissionState,
|
|
49
48
|
} from "#src/yolo-mode";
|
|
50
|
-
|
|
51
|
-
type CreateManagerOptions
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
function createManager(
|
|
56
|
-
config: ScopeConfig,
|
|
57
|
-
agentFiles: Record<string, string> = {},
|
|
58
|
-
options: CreateManagerOptions = {},
|
|
59
|
-
) {
|
|
60
|
-
const baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-test-"));
|
|
61
|
-
const globalConfigPath = join(baseDir, "pi-permissions.jsonc");
|
|
62
|
-
const agentsDir = join(baseDir, "agents");
|
|
63
|
-
|
|
64
|
-
mkdirSync(agentsDir, { recursive: true });
|
|
65
|
-
writeFileSync(
|
|
66
|
-
globalConfigPath,
|
|
67
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
68
|
-
"utf8",
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
for (const [name, content] of Object.entries(agentFiles)) {
|
|
72
|
-
writeFileSync(join(agentsDir, `${name}.md`), content, "utf8");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const manager = new PermissionManager({
|
|
76
|
-
globalConfigPath,
|
|
77
|
-
agentsDir,
|
|
78
|
-
mcpServerNames: options.mcpServerNames,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
manager,
|
|
83
|
-
globalConfigPath,
|
|
84
|
-
cleanup: (): void => {
|
|
85
|
-
rmSync(baseDir, { recursive: true, force: true });
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
49
|
+
import {
|
|
50
|
+
type CreateManagerOptions,
|
|
51
|
+
createManager,
|
|
52
|
+
} from "#test/helpers/manager-harness";
|
|
89
53
|
|
|
90
54
|
type MockHandler = (
|
|
91
55
|
event: Record<string, unknown>,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { MergedScopes } from "#src/scope-merge";
|
|
3
|
+
import { mergeScopesWithOrigins } from "#src/scope-merge";
|
|
4
|
+
|
|
5
|
+
describe("mergeScopesWithOrigins", () => {
|
|
6
|
+
it("returns empty result for empty scopes array", () => {
|
|
7
|
+
const result: MergedScopes = mergeScopesWithOrigins([]);
|
|
8
|
+
expect(result.mergedPermission).toEqual({});
|
|
9
|
+
expect(result.origins.size).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("attributes a string surface value to the contributing scope via the '*' pattern", () => {
|
|
13
|
+
const result = mergeScopesWithOrigins([
|
|
14
|
+
["global", { permission: { bash: "allow" } }],
|
|
15
|
+
]);
|
|
16
|
+
expect(result.mergedPermission).toEqual({ bash: "allow" });
|
|
17
|
+
expect(result.origins.get("bash")?.get("*")).toBe("global");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("attributes each pattern of an object surface value to the contributing scope", () => {
|
|
21
|
+
const result = mergeScopesWithOrigins([
|
|
22
|
+
[
|
|
23
|
+
"project",
|
|
24
|
+
{ permission: { bash: { "git *": "allow", "npm *": "deny" } } },
|
|
25
|
+
],
|
|
26
|
+
]);
|
|
27
|
+
expect(result.mergedPermission).toEqual({
|
|
28
|
+
bash: { "git *": "allow", "npm *": "deny" },
|
|
29
|
+
});
|
|
30
|
+
expect(result.origins.get("bash")?.get("git *")).toBe("project");
|
|
31
|
+
expect(result.origins.get("bash")?.get("npm *")).toBe("project");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it(
|
|
35
|
+
"shallow-merge: patterns not redefined by the higher scope keep their lower-scope origin;" +
|
|
36
|
+
" patterns the higher scope defines switch to the higher scope",
|
|
37
|
+
() => {
|
|
38
|
+
const result = mergeScopesWithOrigins([
|
|
39
|
+
[
|
|
40
|
+
"global",
|
|
41
|
+
{ permission: { bash: { "ls *": "allow", "git *": "allow" } } },
|
|
42
|
+
],
|
|
43
|
+
["project", { permission: { bash: { "git *": "deny" } } }],
|
|
44
|
+
]);
|
|
45
|
+
expect(result.mergedPermission).toEqual({
|
|
46
|
+
bash: { "ls *": "allow", "git *": "deny" },
|
|
47
|
+
});
|
|
48
|
+
// "ls *" was not touched by project — retains global attribution
|
|
49
|
+
expect(result.origins.get("bash")?.get("ls *")).toBe("global");
|
|
50
|
+
// "git *" was overridden by project — switches to project attribution
|
|
51
|
+
expect(result.origins.get("bash")?.get("git *")).toBe("project");
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
it("full replacement (string over object): higher scope re-attributes the entire surface to its own origin", () => {
|
|
56
|
+
const result = mergeScopesWithOrigins([
|
|
57
|
+
["global", { permission: { bash: { "ls *": "allow" } } }],
|
|
58
|
+
["project", { permission: { bash: "deny" } }],
|
|
59
|
+
]);
|
|
60
|
+
expect(result.mergedPermission).toEqual({ bash: "deny" });
|
|
61
|
+
// The string value produces a single "*" pattern for the replacing scope
|
|
62
|
+
expect(result.origins.get("bash")?.get("*")).toBe("project");
|
|
63
|
+
// The former "ls *" pattern from global is gone — origins are replaced, not merged
|
|
64
|
+
expect(result.origins.get("bash")?.has("ls *")).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("full replacement (object over string): higher scope re-attributes the entire surface to its own origin", () => {
|
|
68
|
+
const result = mergeScopesWithOrigins([
|
|
69
|
+
["global", { permission: { bash: "ask" } }],
|
|
70
|
+
["project", { permission: { bash: { "git *": "deny" } } }],
|
|
71
|
+
]);
|
|
72
|
+
expect(result.mergedPermission).toEqual({ bash: { "git *": "deny" } });
|
|
73
|
+
// The object value attributes each pattern to the replacing scope
|
|
74
|
+
expect(result.origins.get("bash")?.get("git *")).toBe("project");
|
|
75
|
+
// The former "*" attribution from global is gone
|
|
76
|
+
expect(result.origins.get("bash")?.has("*")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("applies four-scope precedence in lowest→highest order (global → project → agent → project-agent)", () => {
|
|
80
|
+
const result = mergeScopesWithOrigins([
|
|
81
|
+
["global", { permission: { read: "ask" } }],
|
|
82
|
+
["project", { permission: { write: "deny" } }],
|
|
83
|
+
["agent", { permission: { bash: "deny" } }],
|
|
84
|
+
["project-agent", { permission: { mcp: "allow" } }],
|
|
85
|
+
]);
|
|
86
|
+
expect(result.mergedPermission).toEqual({
|
|
87
|
+
read: "ask",
|
|
88
|
+
write: "deny",
|
|
89
|
+
bash: "deny",
|
|
90
|
+
mcp: "allow",
|
|
91
|
+
});
|
|
92
|
+
expect(result.origins.get("read")?.get("*")).toBe("global");
|
|
93
|
+
expect(result.origins.get("write")?.get("*")).toBe("project");
|
|
94
|
+
expect(result.origins.get("bash")?.get("*")).toBe("agent");
|
|
95
|
+
expect(result.origins.get("mcp")?.get("*")).toBe("project-agent");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("skips scopes with no permission key, contributing nothing to either map", () => {
|
|
99
|
+
const result = mergeScopesWithOrigins([
|
|
100
|
+
["global", {}],
|
|
101
|
+
["project", { permission: { bash: "allow" } }],
|
|
102
|
+
]);
|
|
103
|
+
expect(result.mergedPermission).toEqual({ bash: "allow" });
|
|
104
|
+
expect(result.origins.get("bash")?.get("*")).toBe("project");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("attributes the universal '*' surface like any other (downstream reads origins.get('*')?.get('*') for universalFallbackOrigin)", () => {
|
|
108
|
+
const result = mergeScopesWithOrigins([
|
|
109
|
+
["global", { permission: { "*": "deny" } }],
|
|
110
|
+
["project", { permission: { "*": "allow" } }],
|
|
111
|
+
]);
|
|
112
|
+
expect(result.mergedPermission).toEqual({ "*": "allow" });
|
|
113
|
+
// Both scopes write a string — each is a full replacement; project wins last
|
|
114
|
+
expect(result.origins.get("*")?.get("*")).toBe("project");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { SessionApproval } from "#src/session-approval";
|
|
4
|
+
|
|
5
|
+
describe("SessionApproval", () => {
|
|
6
|
+
describe("single", () => {
|
|
7
|
+
it("stores surface and one pattern", () => {
|
|
8
|
+
const approval = SessionApproval.single("bash", "git *");
|
|
9
|
+
expect(approval.surface).toBe("bash");
|
|
10
|
+
expect(approval.patterns).toEqual(["git *"]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("representativePattern returns the pattern", () => {
|
|
14
|
+
const approval = SessionApproval.single("bash", "git *");
|
|
15
|
+
expect(approval.representativePattern).toBe("git *");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("toGateApproval returns { surface, pattern }", () => {
|
|
19
|
+
const approval = SessionApproval.single("bash", "git *");
|
|
20
|
+
expect(approval.toGateApproval()).toEqual({
|
|
21
|
+
surface: "bash",
|
|
22
|
+
pattern: "git *",
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("multiple", () => {
|
|
28
|
+
it("stores surface and all patterns", () => {
|
|
29
|
+
const approval = SessionApproval.multiple("external_directory", [
|
|
30
|
+
"/outside/a/*",
|
|
31
|
+
"/outside/b/*",
|
|
32
|
+
]);
|
|
33
|
+
expect(approval.surface).toBe("external_directory");
|
|
34
|
+
expect(approval.patterns).toEqual(["/outside/a/*", "/outside/b/*"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("representativePattern returns the first pattern", () => {
|
|
38
|
+
const approval = SessionApproval.multiple("external_directory", [
|
|
39
|
+
"/outside/a/*",
|
|
40
|
+
"/outside/b/*",
|
|
41
|
+
]);
|
|
42
|
+
expect(approval.representativePattern).toBe("/outside/a/*");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("toGateApproval returns { surface, pattern } using the first pattern", () => {
|
|
46
|
+
const approval = SessionApproval.multiple("external_directory", [
|
|
47
|
+
"/outside/a/*",
|
|
48
|
+
"/outside/b/*",
|
|
49
|
+
]);
|
|
50
|
+
expect(approval.toGateApproval()).toEqual({
|
|
51
|
+
surface: "external_directory",
|
|
52
|
+
pattern: "/outside/a/*",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("defensive copy — mutating the source array does not affect patterns", () => {
|
|
57
|
+
const source = ["/outside/a/*", "/outside/b/*"];
|
|
58
|
+
const approval = SessionApproval.multiple("external_directory", source);
|
|
59
|
+
source.push("/outside/c/*");
|
|
60
|
+
expect(approval.patterns).toEqual(["/outside/a/*", "/outside/b/*"]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("empty patterns (degenerate case)", () => {
|
|
65
|
+
it("representativePattern returns undefined", () => {
|
|
66
|
+
const approval = SessionApproval.multiple("external_directory", []);
|
|
67
|
+
expect(approval.representativePattern).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("toGateApproval returns undefined", () => {
|
|
71
|
+
const approval = SessionApproval.multiple("external_directory", []);
|
|
72
|
+
expect(approval.toGateApproval()).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { evaluate } from "#src/rule";
|
|
4
|
+
import { SessionApproval } from "#src/session-approval";
|
|
4
5
|
import { deriveApprovalPattern, SessionRules } from "#src/session-rules";
|
|
5
6
|
|
|
6
7
|
// ── SessionRules ───────────────────────────────────────────────────────────
|
|
@@ -66,6 +67,54 @@ describe("SessionRules", () => {
|
|
|
66
67
|
});
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
describe("record", () => {
|
|
71
|
+
it("records a single-pattern approval as one rule", () => {
|
|
72
|
+
const rules = new SessionRules();
|
|
73
|
+
rules.record(SessionApproval.single("bash", "git *"));
|
|
74
|
+
expect(rules.getRuleset()).toEqual([
|
|
75
|
+
{
|
|
76
|
+
surface: "bash",
|
|
77
|
+
pattern: "git *",
|
|
78
|
+
action: "allow",
|
|
79
|
+
layer: "session",
|
|
80
|
+
origin: "session",
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("records a multi-pattern approval as one rule per pattern", () => {
|
|
86
|
+
const rules = new SessionRules();
|
|
87
|
+
rules.record(
|
|
88
|
+
SessionApproval.multiple("external_directory", [
|
|
89
|
+
"/outside/a/*",
|
|
90
|
+
"/outside/b/*",
|
|
91
|
+
]),
|
|
92
|
+
);
|
|
93
|
+
expect(rules.getRuleset()).toHaveLength(2);
|
|
94
|
+
expect(rules.getRuleset()[0].pattern).toBe("/outside/a/*");
|
|
95
|
+
expect(rules.getRuleset()[1].pattern).toBe("/outside/b/*");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("records each rule with the correct surface", () => {
|
|
99
|
+
const rules = new SessionRules();
|
|
100
|
+
rules.record(
|
|
101
|
+
SessionApproval.multiple("external_directory", [
|
|
102
|
+
"/outside/a/*",
|
|
103
|
+
"/outside/b/*",
|
|
104
|
+
]),
|
|
105
|
+
);
|
|
106
|
+
for (const rule of rules.getRuleset()) {
|
|
107
|
+
expect(rule.surface).toBe("external_directory");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("records nothing for an empty patterns list", () => {
|
|
112
|
+
const rules = new SessionRules();
|
|
113
|
+
rules.record(SessionApproval.multiple("external_directory", []));
|
|
114
|
+
expect(rules.getRuleset()).toEqual([]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
69
118
|
describe("evaluate() integration", () => {
|
|
70
119
|
it("returns allow for a path under an approved directory", () => {
|
|
71
120
|
const session = new SessionRules();
|