@gotgenes/pi-permission-system 8.3.2 → 9.0.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 +35 -0
- package/package.json +1 -1
- package/src/handlers/gates/bash-command.ts +55 -0
- package/src/handlers/gates/bash-external-directory.ts +2 -1
- package/src/handlers/gates/bash-path-extractor.ts +9 -618
- package/src/handlers/gates/bash-path.ts +13 -7
- package/src/handlers/gates/bash-program.ts +727 -0
- package/src/handlers/gates/candidate-check.ts +32 -0
- package/src/handlers/lifecycle.ts +9 -0
- package/src/handlers/permission-gate-handler.ts +21 -8
- package/src/index.ts +30 -11
- package/src/permission-events.ts +3 -2
- package/src/service.ts +17 -4
- package/src/subagent-context.ts +28 -9
- package/test/composition-root.test.ts +398 -0
- package/test/handlers/gates/bash-command.test.ts +167 -0
- package/test/handlers/gates/bash-program.test.ts +107 -0
- package/test/handlers/gates/candidate-check.test.ts +52 -0
- package/test/handlers/lifecycle.test.ts +15 -2
- package/test/handlers/tool-call.test.ts +73 -0
- package/test/helpers/make-fake-pi.ts +95 -0
- package/test/permission-events.test.ts +32 -2
- package/test/permission-system.test.ts +16 -34
- package/test/service.test.ts +25 -6
- package/test/subagent-context.test.ts +40 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [9.0.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v9.0.0...pi-permission-system-v9.0.1) (2026-06-01)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* enumerate top-level bash commands in BashProgram ([cdb41e1](https://github.com/gotgenes/pi-packages/commit/cdb41e1ed03ad2219f5ba0a3ec79130bd39f3686))
|
|
14
|
+
* evaluate each bash sub-command with most-restrictive precedence ([85e48b2](https://github.com/gotgenes/pi-packages/commit/85e48b258dad84756a05fe11615d6d5de68a8659))
|
|
15
|
+
* gate bash command chains per sub-command ([#301](https://github.com/gotgenes/pi-packages/issues/301)) ([3f80097](https://github.com/gotgenes/pi-packages/commit/3f800977a909b2efc2a21ffefe933804b1c0eafd))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
* document per-sub-command bash chain evaluation ([#301](https://github.com/gotgenes/pi-packages/issues/301)) ([e195a70](https://github.com/gotgenes/pi-packages/commit/e195a706d192f3acaeb232c6ed580890ac3c0652))
|
|
21
|
+
|
|
22
|
+
## [9.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.3.2...pi-permission-system-v9.0.0) (2026-06-01)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### ⚠ BREAKING CHANGES
|
|
26
|
+
|
|
27
|
+
* unpublishPermissionsService() now requires the service to remove as its sole argument. The package's public export is service.ts, so this changes the published API surface.
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* scope service teardown to the publishing instance ([#302](https://github.com/gotgenes/pi-packages/issues/302)) ([72180e9](https://github.com/gotgenes/pi-packages/commit/72180e906f7370c842cd5e31a11726c2971fc988))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
* keep the parent's service published across child shutdown ([#302](https://github.com/gotgenes/pi-packages/issues/302)) ([300214c](https://github.com/gotgenes/pi-packages/commit/300214ca21d985bfba7231f261c022c394d8bf5a))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Documentation
|
|
40
|
+
|
|
41
|
+
* document session_start service publication and ready timing ([#302](https://github.com/gotgenes/pi-packages/issues/302)) ([a894fb8](https://github.com/gotgenes/pi-packages/commit/a894fb8d5c2bbc7cd9d33769859d172c5a7dbb73))
|
|
42
|
+
|
|
8
43
|
## [8.3.2](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.3.1...pi-permission-system-v8.3.2) (2026-06-01)
|
|
9
44
|
|
|
10
45
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BashProgram } from "#src/handlers/gates/bash-program";
|
|
2
|
+
import { pickMostRestrictive } from "#src/handlers/gates/candidate-check";
|
|
3
|
+
import type { Rule } from "#src/rule";
|
|
4
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
5
|
+
|
|
6
|
+
/** Function type for checkPermission used by the resolver. */
|
|
7
|
+
type CheckPermissionFn = (
|
|
8
|
+
surface: string,
|
|
9
|
+
input: unknown,
|
|
10
|
+
agentName?: string,
|
|
11
|
+
sessionRules?: Rule[],
|
|
12
|
+
) => PermissionCheckResult;
|
|
13
|
+
|
|
14
|
+
/** Decompose a bash command into its top-level simple-commands. */
|
|
15
|
+
async function decomposeTopLevelCommands(command: string): Promise<string[]> {
|
|
16
|
+
return (await BashProgram.parse(command)).topLevelCommands();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the bash command-pattern decision for a (possibly chained) command.
|
|
21
|
+
*
|
|
22
|
+
* A bash invocation may be a shell program with several commands joined by
|
|
23
|
+
* `&&`, `||`, `;`, `|`, `&`, or newlines. Matching the whole string against the
|
|
24
|
+
* bash patterns lets a denied command ride through on an allowed leading one
|
|
25
|
+
* (issue #301). Instead, decompose the command into its top-level simple-commands
|
|
26
|
+
* and evaluate each on the `bash` surface, then select the most restrictive
|
|
27
|
+
* result (`deny > ask > allow`).
|
|
28
|
+
*
|
|
29
|
+
* The selected result carries the offending sub-command in `command` and its
|
|
30
|
+
* rule in `matchedPattern`, so the prompt, session-approval suggestion, and
|
|
31
|
+
* decision event scope to that command.
|
|
32
|
+
*
|
|
33
|
+
* When decomposition yields no top-level commands (an empty command, a comment,
|
|
34
|
+
* or a bare compound statement), the whole command is evaluated as before, so
|
|
35
|
+
* the surface is never weaker than the previous behavior.
|
|
36
|
+
*
|
|
37
|
+
* `checkPermission` stays synchronous and single-command; only the decomposition
|
|
38
|
+
* is async (tree-sitter). `decompose` is injectable for testing.
|
|
39
|
+
*/
|
|
40
|
+
export async function resolveBashCommandCheck(
|
|
41
|
+
command: string,
|
|
42
|
+
agentName: string | undefined,
|
|
43
|
+
sessionRules: Rule[],
|
|
44
|
+
checkPermission: CheckPermissionFn,
|
|
45
|
+
decompose: (command: string) => Promise<string[]> = decomposeTopLevelCommands,
|
|
46
|
+
): Promise<PermissionCheckResult> {
|
|
47
|
+
const units = await decompose(command);
|
|
48
|
+
const results = units.map((unit) =>
|
|
49
|
+
checkPermission("bash", { command: unit }, agentName, sessionRules),
|
|
50
|
+
);
|
|
51
|
+
return (
|
|
52
|
+
pickMostRestrictive(results) ??
|
|
53
|
+
checkPermission("bash", { command }, agentName, sessionRules)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -4,6 +4,7 @@ import { SessionApproval } from "#src/session-approval";
|
|
|
4
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
5
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
6
6
|
import { extractExternalPathsFromBashCommand } from "./bash-path-extractor";
|
|
7
|
+
import { pickMostRestrictive } from "./candidate-check";
|
|
7
8
|
import type { GateResult } from "./descriptor";
|
|
8
9
|
import { formatBashExternalDirectoryAskPrompt } from "./external-directory-messages";
|
|
9
10
|
import type { ToolCallContext } from "./types";
|
|
@@ -85,7 +86,7 @@ export async function describeBashExternalDirectoryGate(
|
|
|
85
86
|
// This ensures a config-level "deny" rule is not downgraded to "ask" by the
|
|
86
87
|
// generic "*" catch-all that the old path-less checkPermission call returned.
|
|
87
88
|
const worstCheck =
|
|
88
|
-
uncoveredEntries.
|
|
89
|
+
pickMostRestrictive(uncoveredEntries.map(({ check }) => check)) ??
|
|
89
90
|
uncoveredEntries[0].check;
|
|
90
91
|
|
|
91
92
|
const bashExtMessage = formatBashExternalDirectoryAskPrompt(
|