pi-permission-system 0.4.5 → 0.4.7

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 CHANGED
@@ -5,7 +5,27 @@ 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
- ## [Unreleased]
8
+ ## [0.4.7] - 2026-05-04
9
+
10
+ ### Added
11
+ - Documented `PI_PERMISSION_SYSTEM_CONFIG_PATH` and `PI_PERMISSION_SYSTEM_LOGS_DIR` overrides for custom config and log locations.
12
+ - Added artifact validation to release checks for config defaults, schema support, README references, and package script safety.
13
+
14
+ ### Changed
15
+ - Enforced trusted global/system `deny` floors so project-local policy layers can tighten permissions without relaxing higher-trust denies.
16
+ - Switched permission review audit entries to metadata hashes and lengths for prompts, commands, denial reasons, and tool input previews instead of raw sensitive content.
17
+
18
+ ### Fixed
19
+ - Stopped publishing `config.json` in the package so fresh installs use the runtime default with yolo mode off by default (thanks to @Nateowami for PR #18).
20
+
21
+ ## [0.4.6] - 2026-04-28
22
+
23
+ ### Added
24
+ - Added bounded, sanitized tool input previews to permission review logs for non-bash/non-MCP tool calls, inspired by PR #10 from @DevkumarPatel.
25
+
26
+ ### Changed
27
+ - 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.
28
+ - Updated `@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, and `@mariozechner/pi-tui` peer dependencies to `^0.70.5`.
9
29
 
10
30
  ## [0.4.5] - 2026-04-27
11
31
 
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 redacted prompt/input metadata for auditing without writing raw prompts, commands, or tool payload previews
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
@@ -101,6 +102,8 @@ The extension integrates via Pi's lifecycle hooks:
101
102
 
102
103
  **Location:** global Pi extension config (default: `~/.pi/agent/extensions/pi-permission-system/config.json`, respects `PI_CODING_AGENT_DIR`)
103
104
 
105
+ Set `PI_PERMISSION_SYSTEM_CONFIG_PATH` to point this extension at a specific config file when the default global path is not appropriate.
106
+
104
107
  The extension creates this file automatically when it is missing. It controls only extension-local logging behavior:
105
108
 
106
109
  ```json
@@ -117,7 +120,7 @@ The extension creates this file automatically when it is missing. It controls on
117
120
  | `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
118
121
  | `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
119
122
 
120
- Both logs write to files only under the extension directory. No debug output is printed to the terminal.
123
+ Both logs write to files only under the extension directory by default. Set `PI_PERMISSION_SYSTEM_LOGS_DIR` to redirect review/debug logs to a specific directory. No debug output is printed to the terminal.
121
124
 
122
125
  ### Global Policy File
123
126
 
@@ -180,7 +183,7 @@ Project-local files use the same formats as the global policy file and global ag
180
183
  3. Global agent frontmatter
181
184
  4. Project agent frontmatter
182
185
 
183
- Later layers override earlier layers within the same permission category. For wildcard-based sections like `bash`, `mcp`, `skills`, and `special`, matching still follows the extension's existing **last matching rule wins** behavior after the layers are combined.
186
+ Later trusted layers override earlier layers within the same permission category, and project-local layers can tighten policy by adding `deny` rules. Project-local policy cannot relax a `deny` from the global policy file or global agent frontmatter: an `allow` or `ask` in a project policy is ignored when the latest matching trusted layer is `deny`. For wildcard-based sections like `bash`, `mcp`, `skills`, and `special`, matching still follows **last matching rule wins** within the applicable trust boundary, with global/system `deny` rules acting as floors for project-local overrides.
184
187
 
185
188
  ---
186
189
 
@@ -321,7 +324,6 @@ Reserved permission checks:
321
324
  |----------------------|------------------------------------------|
322
325
  | `doom_loop` | Controls doom loop detection behavior |
323
326
  | `external_directory` | Enforces ask/allow/deny decisions for path-bearing built-in tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) when they target paths outside the active working directory |
324
- | `tool_call_limit` | *(schema only, not enforced yet)* |
325
327
 
326
328
  ```jsonc
327
329
  {
@@ -430,9 +432,10 @@ When the extension prompts, denies, or forwards permission requests, it can appe
430
432
  ```text
431
433
  Default global logs directory: ~/.pi/agent/extensions/pi-permission-system/logs/
432
434
  Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs when PI_CODING_AGENT_DIR is set
435
+ Override logs directory: $PI_PERMISSION_SYSTEM_LOGS_DIR when set
433
436
  ```
434
437
 
435
- - `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history
438
+ - `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history, including metadata hashes and lengths for prompts, commands, denial reasons, and tool input previews instead of raw sensitive content
436
439
  - `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
437
440
 
438
441
  ### Architecture
@@ -521,11 +524,14 @@ npx --yes ajv-cli@5 validate \
521
524
 
522
525
  ## Development
523
526
 
527
+ Runtime checks require Node.js 20+; the test suite requires Bun 1.1+.
528
+
524
529
  ```bash
525
- npm run build # Compile TypeScript
526
- npm run lint # Run linter (uses build)
527
- npm run test # Run tests from ./tests
528
- npm run check # Run lint + test
530
+ npm run build # Run TypeScript type checks
531
+ npm run lint # Run local static checks
532
+ npm run validate:artifacts # Validate JSON/schema/example artifacts
533
+ npm run test # Run Bun tests from ./tests
534
+ npm run check # Run static, artifact, and test checks
529
535
  ```
530
536
 
531
537
  ---
package/package.json CHANGED
@@ -1,66 +1,68 @@
1
- {
2
- "name": "pi-permission-system",
3
- "version": "0.4.5",
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.2",
62
- "@mariozechner/pi-coding-agent": "^0.70.2",
63
- "@mariozechner/pi-tui": "^0.70.2",
64
- "@sinclair/typebox": "^0.34.49"
65
- }
66
- }
1
+ {
2
+ "name": "pi-permission-system",
3
+ "version": "0.4.7",
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/config.example.json",
15
+ "schemas/permissions.schema.json",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
22
+ "build": "npm run typecheck",
23
+ "lint": "npm run typecheck",
24
+ "validate:artifacts": "node ./scripts/validate-artifacts.mjs",
25
+ "test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
26
+ "check": "npm run lint && npm run validate:artifacts && npm run test"
27
+ },
28
+ "keywords": [
29
+ "pi-package",
30
+ "pi",
31
+ "pi-extension",
32
+ "pi-coding-agent",
33
+ "coding-agent",
34
+ "permissions",
35
+ "policy",
36
+ "access-control",
37
+ "authorization",
38
+ "security"
39
+ ],
40
+ "author": "MasuRii",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/MasuRii/pi-permission-system.git"
45
+ },
46
+ "homepage": "https://github.com/MasuRii/pi-permission-system#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/MasuRii/pi-permission-system/issues"
49
+ },
50
+ "engines": {
51
+ "node": ">=20",
52
+ "bun": ">=1.1.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "pi": {
58
+ "extensions": [
59
+ "./index.ts"
60
+ ]
61
+ },
62
+ "peerDependencies": {
63
+ "@mariozechner/pi-ai": "^0.72.0",
64
+ "@mariozechner/pi-coding-agent": "^0.72.0",
65
+ "@mariozechner/pi-tui": "^0.72.0",
66
+ "@sinclair/typebox": "^0.34.49"
67
+ }
68
+ }
@@ -53,17 +53,6 @@
53
53
  },
54
54
  "external_directory": {
55
55
  "$ref": "#/$defs/permissionState"
56
- },
57
- "tool_call_limit": {
58
- "oneOf": [
59
- {
60
- "$ref": "#/$defs/permissionState"
61
- },
62
- {
63
- "type": "integer",
64
- "minimum": 0
65
- }
66
- ]
67
56
  }
68
57
  }
69
58
  }
package/src/common.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { homedir } from "node:os";
2
+ import { join, normalize, resolve, sep } from "node:path";
3
+
1
4
  import type { PermissionState } from "./types.js";
2
5
 
3
6
  export function toRecord(value: unknown): Record<string, unknown> {
@@ -21,6 +24,38 @@ export function isPermissionState(value: unknown): value is PermissionState {
21
24
  return value === "allow" || value === "deny" || value === "ask";
22
25
  }
23
26
 
27
+ export function normalizePathForComparison(pathValue: string, cwd: string): string {
28
+ const trimmed = pathValue.trim().replace(/^["']|["']$/g, "");
29
+ if (!trimmed) {
30
+ return "";
31
+ }
32
+
33
+ let normalizedPath = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
34
+
35
+ if (normalizedPath === "~") {
36
+ normalizedPath = homedir();
37
+ } else if (normalizedPath.startsWith("~/") || normalizedPath.startsWith("~\\")) {
38
+ normalizedPath = join(homedir(), normalizedPath.slice(2));
39
+ }
40
+
41
+ const absolutePath = resolve(cwd, normalizedPath);
42
+ const normalizedAbsolutePath = normalize(absolutePath);
43
+ return process.platform === "win32" ? normalizedAbsolutePath.toLowerCase() : normalizedAbsolutePath;
44
+ }
45
+
46
+ export function isPathWithinDirectory(pathValue: string, directory: string): boolean {
47
+ if (!pathValue || !directory) {
48
+ return false;
49
+ }
50
+
51
+ if (pathValue === directory) {
52
+ return true;
53
+ }
54
+
55
+ const prefix = directory.endsWith(sep) ? directory : `${directory}${sep}`;
56
+ return pathValue.startsWith(prefix);
57
+ }
58
+
24
59
  type StackNode = { indent: number; target: Record<string, unknown> };
25
60
 
26
61
  export function parseSimpleYamlMap(input: string): Record<string, unknown> {