@gotgenes/pi-permission-system 9.0.0 → 9.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 +27 -0
- package/package.json +1 -1
- package/src/denial-messages.ts +44 -3
- package/src/handlers/gates/bash-command.ts +57 -0
- package/src/handlers/gates/bash-external-directory.ts +11 -10
- package/src/handlers/gates/bash-path-extractor.ts +9 -618
- package/src/handlers/gates/bash-path.ts +22 -13
- package/src/handlers/gates/bash-program.ts +807 -0
- package/src/handlers/gates/candidate-check.ts +32 -0
- package/src/handlers/permission-gate-handler.ts +38 -8
- package/src/permission-prompts.ts +7 -4
- package/src/types.ts +15 -0
- package/test/denial-messages.test.ts +16 -0
- package/test/handlers/gates/bash-command.test.ts +205 -0
- package/test/handlers/gates/bash-external-directory.test.ts +40 -14
- package/test/handlers/gates/bash-path.test.ts +41 -14
- package/test/handlers/gates/bash-program.test.ts +179 -0
- package/test/handlers/gates/candidate-check.test.ts +52 -0
- package/test/handlers/tool-call.test.ts +111 -0
- package/test/permission-prompts.test.ts +16 -0
|
@@ -3,7 +3,8 @@ import type { Rule } from "#src/rule";
|
|
|
3
3
|
import { SessionApproval } from "#src/session-approval";
|
|
4
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
5
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
6
|
-
import {
|
|
6
|
+
import type { BashProgram } from "./bash-program";
|
|
7
|
+
import { pickMostRestrictive } from "./candidate-check";
|
|
7
8
|
import type { GateResult } from "./descriptor";
|
|
8
9
|
import { formatPathAskPrompt } from "./path";
|
|
9
10
|
import type { ToolCallContext } from "./types";
|
|
@@ -19,8 +20,8 @@ type CheckPermissionFn = (
|
|
|
19
20
|
/**
|
|
20
21
|
* Build a pure descriptor for the cross-cutting path permission gate (bash).
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* Reads path-candidate tokens from the injected `BashProgram` (the broader
|
|
24
|
+
* `path`-rule filter, accepting dot-files and relative paths). Evaluates each
|
|
24
25
|
* token against the `path` permission surface and returns the most
|
|
25
26
|
* restrictive result.
|
|
26
27
|
*
|
|
@@ -29,24 +30,28 @@ type CheckPermissionFn = (
|
|
|
29
30
|
* Returns a `GateBypass` when all tokens are session-covered.
|
|
30
31
|
* Returns a `GateDescriptor` for the most restrictive token needing a check.
|
|
31
32
|
*/
|
|
32
|
-
export
|
|
33
|
+
export function describeBashPathGate(
|
|
33
34
|
tcc: ToolCallContext,
|
|
35
|
+
bashProgram: BashProgram | null,
|
|
34
36
|
checkPermission: CheckPermissionFn,
|
|
35
37
|
getSessionRuleset: () => Rule[],
|
|
36
|
-
):
|
|
38
|
+
): GateResult {
|
|
37
39
|
if (tcc.toolName !== "bash") return null;
|
|
38
40
|
|
|
39
41
|
const command = getNonEmptyString(toRecord(tcc.input).command);
|
|
40
42
|
if (!command) return null;
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
if (!bashProgram) return null;
|
|
45
|
+
|
|
46
|
+
const tokens = bashProgram.pathTokens();
|
|
43
47
|
if (tokens.length === 0) return null;
|
|
44
48
|
|
|
45
49
|
// Check each token against path rules with session rules appended.
|
|
46
50
|
const sessionRules = getSessionRuleset();
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
// Tokens whose resolved state needs a check (deny/ask), paired with the
|
|
53
|
+
// token that produced them so the descriptor can derive its pattern.
|
|
54
|
+
const uncovered: Array<{ token: string; check: PermissionCheckResult }> = [];
|
|
50
55
|
let allSessionCovered = true;
|
|
51
56
|
|
|
52
57
|
for (const token of tokens) {
|
|
@@ -70,13 +75,11 @@ export async function describeBashPathGate(
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
if (check.state === "deny") {
|
|
73
|
-
|
|
74
|
-
worstToken = token;
|
|
78
|
+
uncovered.push({ token, check });
|
|
75
79
|
break; // Short-circuit on deny.
|
|
76
80
|
}
|
|
77
|
-
if (check.state === "ask"
|
|
78
|
-
|
|
79
|
-
worstToken = token;
|
|
81
|
+
if (check.state === "ask") {
|
|
82
|
+
uncovered.push({ token, check });
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -99,6 +102,12 @@ export async function describeBashPathGate(
|
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
// Pick the most restrictive (deny > ask > allow, first-wins) uncovered token.
|
|
106
|
+
const worstCheck = pickMostRestrictive(uncovered.map(({ check }) => check));
|
|
107
|
+
const worstToken = worstCheck
|
|
108
|
+
? (uncovered.find(({ check }) => check === worstCheck)?.token ?? null)
|
|
109
|
+
: null;
|
|
110
|
+
|
|
102
111
|
// All tokens evaluate to allow — no restriction.
|
|
103
112
|
if (!worstCheck || !worstToken) return null;
|
|
104
113
|
|