pi-permission-system 0.4.5 → 0.4.6
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 +9 -0
- package/README.md +2 -1
- package/package.json +66 -66
- package/src/index.ts +37 -23
- package/src/logging.ts +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.6] - 2026-04-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added bounded, sanitized tool input previews to permission review logs for non-bash/non-MCP tool calls, inspired by PR #10 from @DevkumarPatel.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Reused the extension's safe JSON serialization path for generic tool approval previews so circular values and BigInts are summarized without raw full-input logging.
|
|
17
|
+
- Updated `@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, and `@mariozechner/pi-tui` peer dependencies to `^0.70.5`.
|
|
18
|
+
|
|
10
19
|
## [0.4.5] - 2026-04-27
|
|
11
20
|
|
|
12
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -93,6 +93,7 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
93
93
|
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled by exact registered name instead of private built-in hardcodes
|
|
94
94
|
- When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
|
|
95
95
|
- Generic extension-tool approval prompts include a bounded input preview; built-in file tools use concise human-readable summaries instead of raw multiline JSON
|
|
96
|
+
- Permission review logs include bounded `toolInputPreview` values for non-bash/non-MCP tool calls so approvals can be audited without writing raw full payloads
|
|
96
97
|
- Path-bearing file tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) evaluate `special.external_directory` before their normal tool permission when an explicit path points outside `ctx.cwd`
|
|
97
98
|
|
|
98
99
|
## Configuration
|
|
@@ -432,7 +433,7 @@ Default global logs directory: ~/.pi/agent/extensions/pi-permission-system/logs/
|
|
|
432
433
|
Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs when PI_CODING_AGENT_DIR is set
|
|
433
434
|
```
|
|
434
435
|
|
|
435
|
-
- `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history
|
|
436
|
+
- `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history, including bounded `toolInputPreview` values for non-bash/non-MCP tool calls
|
|
436
437
|
- `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
|
|
437
438
|
|
|
438
439
|
### Architecture
|
package/package.json
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-permission-system",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.ts"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src",
|
|
13
|
-
"tests",
|
|
14
|
-
"config.json",
|
|
15
|
-
"config/config.example.json",
|
|
16
|
-
"schemas/permissions.schema.json",
|
|
17
|
-
"README.md",
|
|
18
|
-
"CHANGELOG.md",
|
|
19
|
-
"LICENSE"
|
|
20
|
-
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
|
|
23
|
-
"lint": "npm run build",
|
|
24
|
-
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
25
|
-
"check": "npm run lint && npm run test"
|
|
26
|
-
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"pi-package",
|
|
29
|
-
"pi",
|
|
30
|
-
"pi-extension",
|
|
31
|
-
"pi-coding-agent",
|
|
32
|
-
"coding-agent",
|
|
33
|
-
"permissions",
|
|
34
|
-
"policy",
|
|
35
|
-
"access-control",
|
|
36
|
-
"authorization",
|
|
37
|
-
"security"
|
|
38
|
-
],
|
|
39
|
-
"author": "MasuRii",
|
|
40
|
-
"license": "MIT",
|
|
41
|
-
"repository": {
|
|
42
|
-
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
44
|
-
},
|
|
45
|
-
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
46
|
-
"bugs": {
|
|
47
|
-
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
48
|
-
},
|
|
49
|
-
"engines": {
|
|
50
|
-
"node": ">=20"
|
|
51
|
-
},
|
|
52
|
-
"publishConfig": {
|
|
53
|
-
"access": "public"
|
|
54
|
-
},
|
|
55
|
-
"pi": {
|
|
56
|
-
"extensions": [
|
|
57
|
-
"./index.ts"
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
"peerDependencies": {
|
|
61
|
-
"@mariozechner/pi-ai": "^0.70.
|
|
62
|
-
"@mariozechner/pi-coding-agent": "^0.70.
|
|
63
|
-
"@mariozechner/pi-tui": "^0.70.
|
|
64
|
-
"@sinclair/typebox": "^0.34.49"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-permission-system",
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"tests",
|
|
14
|
+
"config.json",
|
|
15
|
+
"config/config.example.json",
|
|
16
|
+
"schemas/permissions.schema.json",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
|
|
23
|
+
"lint": "npm run build",
|
|
24
|
+
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
25
|
+
"check": "npm run lint && npm run test"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"pi-package",
|
|
29
|
+
"pi",
|
|
30
|
+
"pi-extension",
|
|
31
|
+
"pi-coding-agent",
|
|
32
|
+
"coding-agent",
|
|
33
|
+
"permissions",
|
|
34
|
+
"policy",
|
|
35
|
+
"access-control",
|
|
36
|
+
"authorization",
|
|
37
|
+
"security"
|
|
38
|
+
],
|
|
39
|
+
"author": "MasuRii",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"pi": {
|
|
56
|
+
"extensions": [
|
|
57
|
+
"./index.ts"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@mariozechner/pi-ai": "^0.70.5",
|
|
62
|
+
"@mariozechner/pi-coding-agent": "^0.70.5",
|
|
63
|
+
"@mariozechner/pi-tui": "^0.70.5",
|
|
64
|
+
"@sinclair/typebox": "^0.34.49"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
savePermissionSystemConfig,
|
|
23
23
|
type PermissionSystemExtensionConfig,
|
|
24
24
|
} from "./extension-config.js";
|
|
25
|
-
import { createPermissionSystemLogger } from "./logging.js";
|
|
25
|
+
import { createPermissionSystemLogger, safeJsonStringify } from "./logging.js";
|
|
26
26
|
import { registerPermissionSystemCommand } from "./config-modal.js";
|
|
27
27
|
import {
|
|
28
28
|
createPermissionForwardingLocation,
|
|
@@ -69,6 +69,7 @@ type PermissionRequestEvent = {
|
|
|
69
69
|
path?: string;
|
|
70
70
|
command?: string;
|
|
71
71
|
target?: string;
|
|
72
|
+
toolInputPreview?: string;
|
|
72
73
|
agentName?: string | null;
|
|
73
74
|
};
|
|
74
75
|
|
|
@@ -313,6 +314,7 @@ function formatUserDeniedReason(result: PermissionCheckResult, denialReason?: st
|
|
|
313
314
|
}
|
|
314
315
|
|
|
315
316
|
const TOOL_INPUT_PREVIEW_MAX_LENGTH = 200;
|
|
317
|
+
const TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH = 1000;
|
|
316
318
|
const TOOL_TEXT_SUMMARY_MAX_LENGTH = 80;
|
|
317
319
|
|
|
318
320
|
function truncateInlineText(value: string, maxLength: number): string {
|
|
@@ -406,30 +408,17 @@ function formatSearchInputForPrompt(toolName: string, input: Record<string, unkn
|
|
|
406
408
|
return parts.length > 0 ? `for ${parts.join(", ")}` : "";
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
return "";
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (typeof input === "object" && !Array.isArray(input) && Object.keys(input as Record<string, unknown>).length === 0) {
|
|
415
|
-
return "";
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
let serialized: string;
|
|
419
|
-
try {
|
|
420
|
-
serialized = JSON.stringify(input);
|
|
421
|
-
} catch {
|
|
422
|
-
return "";
|
|
423
|
-
}
|
|
424
|
-
|
|
411
|
+
function serializeToolInputPreview(input: unknown): string {
|
|
412
|
+
const serialized = safeJsonStringify(input);
|
|
425
413
|
if (!serialized || serialized === "{}" || serialized === "null") {
|
|
426
414
|
return "";
|
|
427
415
|
}
|
|
428
416
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
417
|
+
return serialized.replace(/\s+/g, " ").trim();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function formatJsonInputForPrompt(input: unknown): string {
|
|
421
|
+
const inline = serializeToolInputPreview(input);
|
|
433
422
|
return inline ? `with input ${truncateInlineText(inline, TOOL_INPUT_PREVIEW_MAX_LENGTH)}` : "";
|
|
434
423
|
}
|
|
435
424
|
|
|
@@ -519,10 +508,29 @@ function formatExternalDirectoryUserDeniedReason(
|
|
|
519
508
|
return `User denied external directory access for tool '${toolName}' path '${pathValue}'.${reasonSuffix} ${formatExternalDirectoryHardStopHint()}`;
|
|
520
509
|
}
|
|
521
510
|
|
|
522
|
-
function
|
|
511
|
+
function formatGenericToolInputForLog(input: unknown): string | undefined {
|
|
512
|
+
const inline = serializeToolInputPreview(input);
|
|
513
|
+
return inline ? `input ${truncateInlineText(inline, TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH)}` : undefined;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getToolInputPreviewForLog(result: PermissionCheckResult, input: unknown): string | undefined {
|
|
517
|
+
if (result.toolName === "bash" || result.toolName === "mcp" || result.source === "mcp") {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (PATH_BEARING_TOOLS.has(result.toolName)) {
|
|
522
|
+
const inputPreview = formatToolInputForPrompt(result.toolName, input);
|
|
523
|
+
return inputPreview ? truncateInlineText(inputPreview, TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH) : undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return formatGenericToolInputForLog(input);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function getPermissionLogContext(result: PermissionCheckResult, input: unknown): { command?: string; target?: string; toolInputPreview?: string } {
|
|
523
530
|
return {
|
|
524
531
|
command: result.command,
|
|
525
532
|
target: result.target,
|
|
533
|
+
toolInputPreview: getToolInputPreviewForLog(result, input),
|
|
526
534
|
};
|
|
527
535
|
}
|
|
528
536
|
|
|
@@ -1114,6 +1122,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1114
1122
|
path?: string;
|
|
1115
1123
|
command?: string;
|
|
1116
1124
|
target?: string;
|
|
1125
|
+
toolInputPreview?: string;
|
|
1117
1126
|
resolution?: string;
|
|
1118
1127
|
denialReason?: string;
|
|
1119
1128
|
},
|
|
@@ -1129,6 +1138,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1129
1138
|
path: details.path ?? null,
|
|
1130
1139
|
command: details.command ?? null,
|
|
1131
1140
|
target: details.target ?? null,
|
|
1141
|
+
toolInputPreview: details.toolInputPreview ?? null,
|
|
1132
1142
|
resolution: details.resolution ?? null,
|
|
1133
1143
|
denialReason: details.denialReason ?? null,
|
|
1134
1144
|
});
|
|
@@ -1147,6 +1157,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1147
1157
|
path?: string;
|
|
1148
1158
|
command?: string;
|
|
1149
1159
|
target?: string;
|
|
1160
|
+
toolInputPreview?: string;
|
|
1150
1161
|
},
|
|
1151
1162
|
): Promise<PermissionPromptDecision> => {
|
|
1152
1163
|
if (shouldAutoApprovePermissionState("ask", extensionConfig)) {
|
|
@@ -1162,6 +1173,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1162
1173
|
path: details.path,
|
|
1163
1174
|
command: details.command,
|
|
1164
1175
|
target: details.target,
|
|
1176
|
+
toolInputPreview: details.toolInputPreview,
|
|
1165
1177
|
agentName: details.agentName,
|
|
1166
1178
|
});
|
|
1167
1179
|
return { approved: true, state: "approved" };
|
|
@@ -1179,6 +1191,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1179
1191
|
path: details.path,
|
|
1180
1192
|
command: details.command,
|
|
1181
1193
|
target: details.target,
|
|
1194
|
+
toolInputPreview: details.toolInputPreview,
|
|
1182
1195
|
agentName: details.agentName,
|
|
1183
1196
|
});
|
|
1184
1197
|
|
|
@@ -1199,6 +1212,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1199
1212
|
path: details.path,
|
|
1200
1213
|
command: details.command,
|
|
1201
1214
|
target: details.target,
|
|
1215
|
+
toolInputPreview: details.toolInputPreview,
|
|
1202
1216
|
agentName: details.agentName,
|
|
1203
1217
|
});
|
|
1204
1218
|
|
|
@@ -1563,7 +1577,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1563
1577
|
}
|
|
1564
1578
|
|
|
1565
1579
|
const check = permissionManager.checkPermission(toolName, input, agentName ?? undefined);
|
|
1566
|
-
const permissionLogContext = getPermissionLogContext(check);
|
|
1580
|
+
const permissionLogContext = getPermissionLogContext(check, input);
|
|
1567
1581
|
|
|
1568
1582
|
if (check.state === "deny") {
|
|
1569
1583
|
writeReviewLog("permission_request.blocked", {
|
package/src/logging.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type PermissionSystemExtensionConfig,
|
|
10
10
|
} from "./extension-config.js";
|
|
11
11
|
|
|
12
|
-
function safeJsonStringify(value: unknown): string {
|
|
12
|
+
export function safeJsonStringify(value: unknown): string | undefined {
|
|
13
13
|
const seen = new WeakSet<object>();
|
|
14
14
|
return JSON.stringify(value, (_key, currentValue) => {
|
|
15
15
|
if (currentValue instanceof Error) {
|
|
@@ -66,6 +66,9 @@ export function createPermissionSystemLogger(options: PermissionSystemLoggerOpti
|
|
|
66
66
|
event,
|
|
67
67
|
...details,
|
|
68
68
|
});
|
|
69
|
+
if (!line) {
|
|
70
|
+
return `Failed to write permission-system ${stream} log '${path}': event could not be serialized.`;
|
|
71
|
+
}
|
|
69
72
|
appendFileSync(path, `${line}\n`, "utf-8");
|
|
70
73
|
return undefined;
|
|
71
74
|
} catch (error) {
|