@gotgenes/pi-permission-system 7.3.0 → 7.3.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 +12 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/extension-config.ts +1 -1
- package/src/handlers/before-agent-start.ts +1 -0
- package/src/handlers/gates/bash-path-extractor.ts +0 -8
- package/src/handlers/lifecycle.ts +1 -0
- package/src/handlers/permission-gate-handler.ts +1 -0
- package/src/permission-dialog.ts +0 -6
- package/src/permission-manager.ts +1 -1
- package/src/permission-merge.ts +1 -1
- package/src/skill-prompt-sanitizer.ts +0 -27
- package/src/types.ts +0 -11
- package/test/handlers/before-agent-start.test.ts +0 -1
- package/test/handlers/gates/bash-external-directory.test.ts +17 -12
- package/test/handlers/gates/skill-read.test.ts +0 -2
- package/test/handlers/input-events.test.ts +0 -1
- package/test/handlers/input.test.ts +0 -1
- package/test/handlers/tool-call-events.test.ts +1 -1
- package/test/handlers/tool-call.test.ts +1 -1
- package/test/permission-events.test.ts +1 -1
- package/test/permission-session.test.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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
|
+
## [7.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.3.0...pi-permission-system-v7.3.1) (2026-05-26)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* resolve pre-existing lint errors in pi-autoformat and pi-permission-system ([68fd516](https://github.com/gotgenes/pi-packages/commit/68fd516e33ddbb9a5e37ef19e949ee9ecdc37252))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* update subagent integration docs for native permission bridge ([#101](https://github.com/gotgenes/pi-packages/issues/101)) ([0bd456b](https://github.com/gotgenes/pi-packages/commit/0bd456befa8ea6918e74f4393d844868795edc77))
|
|
19
|
+
|
|
8
20
|
## [7.3.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.2.0...pi-permission-system-v7.3.0) (2026-05-25)
|
|
9
21
|
|
|
10
22
|
|
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
|
|
|
20
20
|
- **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once
|
|
21
21
|
- **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
|
|
22
22
|
- **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
|
|
23
|
+
- **Native [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) integration** — in-process child sessions register with the permission system automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
|
|
23
24
|
|
|
24
25
|
## Install
|
|
25
26
|
|
package/package.json
CHANGED
package/src/extension-config.ts
CHANGED
|
@@ -41,6 +41,7 @@ export function shouldExposeTool(
|
|
|
41
41
|
*/
|
|
42
42
|
export class AgentPrepHandler {
|
|
43
43
|
constructor(
|
|
44
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
|
|
44
45
|
private readonly session: PermissionSession,
|
|
45
46
|
private readonly toolRegistry: ToolRegistry,
|
|
46
47
|
) {}
|
|
@@ -48,14 +48,6 @@ function getParser(): Promise<TSParser> {
|
|
|
48
48
|
return parserPromise;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
* Reset the cached parser promise. Only used by tests to avoid
|
|
53
|
-
* cross-test pollution or to inject a mock parser.
|
|
54
|
-
*/
|
|
55
|
-
function resetParserForTesting(): void {
|
|
56
|
-
parserPromise = null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
// ── AST walker ─────────────────────────────────────────────────────────────
|
|
60
52
|
|
|
61
53
|
/**
|
|
@@ -22,6 +22,7 @@ interface ResourcesDiscoverPayload {
|
|
|
22
22
|
*/
|
|
23
23
|
export class SessionLifecycleHandler {
|
|
24
24
|
constructor(
|
|
25
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
|
|
25
26
|
private readonly session: PermissionSession,
|
|
26
27
|
private readonly cleanupRpc: () => void,
|
|
27
28
|
) {}
|
|
@@ -47,6 +47,7 @@ interface InputPayload {
|
|
|
47
47
|
*/
|
|
48
48
|
export class PermissionGateHandler {
|
|
49
49
|
constructor(
|
|
50
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
|
|
50
51
|
private readonly session: PermissionSession,
|
|
51
52
|
private readonly events: PermissionEventBus,
|
|
52
53
|
private readonly toolRegistry: ToolRegistry,
|
package/src/permission-dialog.ts
CHANGED
|
@@ -25,12 +25,6 @@ const APPROVE_OPTION = "Yes";
|
|
|
25
25
|
const APPROVE_FOR_SESSION_OPTION = "Yes, for this session";
|
|
26
26
|
const DENY_OPTION = "No";
|
|
27
27
|
const DENY_WITH_REASON_OPTION = "No, provide reason";
|
|
28
|
-
const PERMISSION_DECISION_OPTIONS = [
|
|
29
|
-
APPROVE_OPTION,
|
|
30
|
-
APPROVE_FOR_SESSION_OPTION,
|
|
31
|
-
DENY_OPTION,
|
|
32
|
-
DENY_WITH_REASON_OPTION,
|
|
33
|
-
] as const;
|
|
34
28
|
|
|
35
29
|
export function normalizePermissionDenialReason(
|
|
36
30
|
value: unknown,
|
|
@@ -120,7 +120,7 @@ export class PermissionManager {
|
|
|
120
120
|
// existing patterns from lower scopes keep their earlier origin.
|
|
121
121
|
if (!origins.has(surface)) origins.set(surface, new Map());
|
|
122
122
|
for (const pattern of Object.keys(value)) {
|
|
123
|
-
origins.get(surface)
|
|
123
|
+
origins.get(surface)?.set(pattern, scopeName);
|
|
124
124
|
}
|
|
125
125
|
} else {
|
|
126
126
|
// Full replacement: this scope takes over the entire surface entry.
|
package/src/permission-merge.ts
CHANGED
|
@@ -92,33 +92,6 @@ function parseSkillEntries(sectionBody: string): ParsedSkillPromptEntry[] {
|
|
|
92
92
|
return entries;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function parseSkillPromptSection(prompt: string): SkillPromptSection | null {
|
|
96
|
-
const start = prompt.indexOf(AVAILABLE_SKILLS_OPEN_TAG);
|
|
97
|
-
if (start === -1) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const closeStart = prompt.indexOf(
|
|
102
|
-
AVAILABLE_SKILLS_CLOSE_TAG,
|
|
103
|
-
start + AVAILABLE_SKILLS_OPEN_TAG.length,
|
|
104
|
-
);
|
|
105
|
-
if (closeStart === -1) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const end = closeStart + AVAILABLE_SKILLS_CLOSE_TAG.length;
|
|
110
|
-
const sectionBody = prompt.slice(
|
|
111
|
-
start + AVAILABLE_SKILLS_OPEN_TAG.length,
|
|
112
|
-
closeStart,
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
start,
|
|
117
|
-
end,
|
|
118
|
-
entries: parseSkillEntries(sectionBody),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
95
|
export function parseAllSkillPromptSections(
|
|
123
96
|
prompt: string,
|
|
124
97
|
): SkillPromptSection[] {
|
package/src/types.ts
CHANGED
|
@@ -14,17 +14,6 @@ export type FlatPermissionConfig = Record<
|
|
|
14
14
|
PermissionState | Record<string, PermissionState>
|
|
15
15
|
>;
|
|
16
16
|
|
|
17
|
-
type BuiltInToolName =
|
|
18
|
-
| "bash"
|
|
19
|
-
| "read"
|
|
20
|
-
| "write"
|
|
21
|
-
| "edit"
|
|
22
|
-
| "grep"
|
|
23
|
-
| "find"
|
|
24
|
-
| "ls";
|
|
25
|
-
|
|
26
|
-
type SpecialPermissionName = "external_directory";
|
|
27
|
-
|
|
28
17
|
/**
|
|
29
18
|
* Per-scope permission config shape after loading and validation.
|
|
30
19
|
* Holds only the flat permission map — all policy is expressed there.
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "#src/handlers/before-agent-start";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionState } from "#src/types";
|
|
11
10
|
|
|
12
11
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
13
12
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
@@ -101,12 +101,15 @@ describe("describeBashExternalDirectoryGate", () => {
|
|
|
101
101
|
it("uses config-level checkPermission for the policy state", async () => {
|
|
102
102
|
const checkPermission = vi
|
|
103
103
|
.fn()
|
|
104
|
-
.mockImplementation(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
.mockImplementation(
|
|
105
|
+
(_surface: string, input: Record<string, unknown>) => {
|
|
106
|
+
// Path-specific check returns session for coverage filtering
|
|
107
|
+
if (input.path)
|
|
108
|
+
return makeCheckResult("allow", { source: "special" });
|
|
109
|
+
// Config-level check (no path) returns deny
|
|
110
|
+
return makeCheckResult("deny");
|
|
111
|
+
},
|
|
112
|
+
);
|
|
110
113
|
const result = await describeBashExternalDirectoryGate(
|
|
111
114
|
makeTcc(),
|
|
112
115
|
checkPermission,
|
|
@@ -172,12 +175,14 @@ describe("describeBashExternalDirectoryGate", () => {
|
|
|
172
175
|
it("only includes uncovered paths when some are session-covered", async () => {
|
|
173
176
|
const checkPermission = vi
|
|
174
177
|
.fn()
|
|
175
|
-
.mockImplementation(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
.mockImplementation(
|
|
179
|
+
(_surface: string, input: Record<string, unknown>) => {
|
|
180
|
+
if (input.path === "/outside/a.ts") {
|
|
181
|
+
return makeCheckResult("allow", { source: "session" });
|
|
182
|
+
}
|
|
183
|
+
return makeCheckResult("ask");
|
|
184
|
+
},
|
|
185
|
+
);
|
|
181
186
|
const result = await describeBashExternalDirectoryGate(
|
|
182
187
|
makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
|
|
183
188
|
checkPermission,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import type { GateDescriptor } from "#src/handlers/gates/descriptor";
|
|
4
2
|
import { describeSkillReadGate } from "#src/handlers/gates/skill-read";
|
|
5
3
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
6
4
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
@@ -9,7 +9,6 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
|
9
9
|
import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
|
|
10
10
|
import type { PermissionSession } from "#src/permission-session";
|
|
11
11
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
12
|
-
import type { PermissionState } from "#src/types";
|
|
13
12
|
|
|
14
13
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
15
14
|
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "#src/handlers/permission-gate-handler";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionState } from "#src/types";
|
|
11
10
|
|
|
12
11
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
13
12
|
|
|
@@ -10,7 +10,7 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
|
10
10
|
import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
|
|
11
11
|
import type { PermissionSession } from "#src/permission-session";
|
|
12
12
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
13
|
-
import type { PermissionCheckResult
|
|
13
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
14
14
|
|
|
15
15
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
16
16
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "#src/handlers/permission-gate-handler";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionCheckResult
|
|
10
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
11
11
|
|
|
12
12
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
13
13
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
@@ -264,7 +264,7 @@ describe("piPermissionSystemExtension ready event wiring", () => {
|
|
|
264
264
|
mkdirSync(join(baseDir, "agents"), { recursive: true });
|
|
265
265
|
writeFileSync(
|
|
266
266
|
globalConfigPath,
|
|
267
|
-
JSON.stringify({ permission: { "*": "ask" } })
|
|
267
|
+
`${JSON.stringify({ permission: { "*": "ask" } })}\n`,
|
|
268
268
|
"utf8",
|
|
269
269
|
);
|
|
270
270
|
process.env.PI_CODING_AGENT_DIR = baseDir;
|
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
} from "#src/permission-session";
|
|
39
39
|
import type { SessionLogger } from "#src/session-logger";
|
|
40
40
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
41
|
-
import type { PermissionCheckResult } from "#src/types";
|
|
42
41
|
|
|
43
42
|
function makeSkillEntry(
|
|
44
43
|
name: string,
|