pi-permission-system 0.4.6 → 0.4.8
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 +31 -0
- package/README.md +174 -45
- package/package.json +11 -9
- package/schemas/permissions.schema.json +1 -12
- package/src/common.ts +35 -0
- package/src/config-modal.ts +146 -231
- package/src/extension-config.ts +25 -9
- package/src/index.ts +157 -73
- package/src/logging.ts +7 -8
- package/src/model-option-compatibility.ts +8 -5
- package/src/permission-forwarding.ts +29 -0
- package/src/permission-manager.ts +216 -76
- package/src/skill-prompt-sanitizer.ts +2 -34
- package/src/types-shims.d.ts +6 -0
- package/src/yolo-mode-api.ts +43 -0
- package/src/zellij-modal.ts +1 -1
- package/tests/config-modal.test.ts +63 -122
- package/tests/permission-system.test.ts +303 -18
- package/config.json +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.8] - 2026-05-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added runtime yolo-mode control through the `/permission-system` settings modal and `globalThis.__piPermissionSystem` for other extensions.
|
|
14
|
+
- Added wildcard support for `permission.tools` so direct tools from any extension can be controlled without adapter-specific hardcoding.
|
|
15
|
+
- Added `PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR` so global policy and global agent override lookup can be pointed at a different agent root when needed.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Simplified `/permission-system` to only open the settings modal, removing the previous `show`, `path`, `reset`, `help`, and `yolo ...` subcommands.
|
|
19
|
+
- Expanded the README with an OpenCode-to-Pi migration guide covering agent file placement, frontmatter structure, permission mapping, Pi-specific `tools` vs `mcp` behavior, and policy-root overrides.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Removed artifact validation that forced local runtime `config.json` values to match release defaults.
|
|
23
|
+
- Restored requested bash command text in permission review log entries while keeping prompts and generic tool payload previews redacted.
|
|
24
|
+
- Clarified last-declared-match wildcard precedence in the README and fixed wildcard-order examples/recipes so documented `tools` and `mcp` examples now match runtime behavior (thanks to @Nateowami for issue #17 and @gotgenes for the rule-order diagnosis).
|
|
25
|
+
- Replaced the removed `session_switch` lifecycle subscription with a single supported `session_start` refresh path so session changes rebuild runtime permission state without duplicate handlers (thanks to @gotgenes for PR #11).
|
|
26
|
+
- Removed stale README wording around exact-name tool matching and refreshed the documented architecture/module list to match the current extension layout.
|
|
27
|
+
|
|
28
|
+
## [0.4.7] - 2026-05-04
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Documented `PI_PERMISSION_SYSTEM_CONFIG_PATH` and `PI_PERMISSION_SYSTEM_LOGS_DIR` overrides for custom config and log locations.
|
|
32
|
+
- Added artifact validation to release checks for config defaults, schema support, README references, and package script safety.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Enforced trusted global/system `deny` floors so project-local policy layers can tighten permissions without relaxing higher-trust denies.
|
|
36
|
+
- Switched permission review audit entries to metadata hashes and lengths for prompts, commands, denial reasons, and tool input previews instead of raw sensitive content.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- 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).
|
|
40
|
+
|
|
10
41
|
## [0.4.6] - 2026-04-28
|
|
11
42
|
|
|
12
43
|
### Added
|
package/README.md
CHANGED
|
@@ -6,6 +6,87 @@ Permission enforcement extension for the Pi coding agent that provides centraliz
|
|
|
6
6
|
|
|
7
7
|
<img width="1360" height="752" alt="image" src="https://github.com/user-attachments/assets/3e85190a-17fa-4d94-ac8e-efa54337df5d" />
|
|
8
8
|
|
|
9
|
+
## Coming from OpenCode?
|
|
10
|
+
|
|
11
|
+
Yes — this extension was designed so OpenCode-style agent permission policies can be ported into Pi with minimal friction.
|
|
12
|
+
|
|
13
|
+
### Start here
|
|
14
|
+
|
|
15
|
+
| If you have this in OpenCode | In Pi, use this |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Agent markdown file | `~/.pi/agent/agents/<agent-name>.md` (respects `PI_CODING_AGENT_DIR`) |
|
|
18
|
+
| YAML frontmatter | Same place: top of the markdown file |
|
|
19
|
+
| Agent instructions / system prompt body | Same file, below frontmatter |
|
|
20
|
+
| Agent permission rules | `permission:` inside that same frontmatter |
|
|
21
|
+
|
|
22
|
+
### Important compatibility notes
|
|
23
|
+
|
|
24
|
+
- **Agents are still markdown files with YAML frontmatter.**
|
|
25
|
+
- **Wildcard permissions still use last-match-wins ordering.**
|
|
26
|
+
- **Keep frontmatter simple when porting.** This extension intentionally supports `key: value` scalars and nested maps, not full YAML features like arrays, anchors, or multiline scalars.
|
|
27
|
+
|
|
28
|
+
### Minimal Pi agent example
|
|
29
|
+
|
|
30
|
+
```md
|
|
31
|
+
---
|
|
32
|
+
name: my-agent
|
|
33
|
+
mode: primary
|
|
34
|
+
description: My ported agent
|
|
35
|
+
permission:
|
|
36
|
+
tools:
|
|
37
|
+
read: allow
|
|
38
|
+
grep: allow
|
|
39
|
+
bash:
|
|
40
|
+
"*": ask
|
|
41
|
+
mcp:
|
|
42
|
+
"*": ask
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
Your agent instructions go here.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Compatibility matrix
|
|
49
|
+
|
|
50
|
+
| OpenCode concept | Pi equivalent with this extension | Compatibility | Porting notes |
|
|
51
|
+
|---|---|---:|---|
|
|
52
|
+
| Agent markdown files with YAML frontmatter | `~/.pi/agent/agents/<agent-name>.md` | High | Your agent-local `permission:` frontmatter pattern carries over cleanly. |
|
|
53
|
+
| Wildcard precedence | Same last-declared-match-wins behavior | High | Broad rules first, specific overrides later. |
|
|
54
|
+
| `bash` permission rules | `permission.bash` | High | Command-pattern gating ports cleanly. |
|
|
55
|
+
| Per-tool permission rules like `read`, `grep`, `list`, `task`, or arbitrary extension tool names | `permission.tools` | Medium-High | Pi groups registered tool names under `tools`, including built-ins and extension tools. |
|
|
56
|
+
| `external_directory` | `permission.special.external_directory` | Medium | Same idea, different location. |
|
|
57
|
+
| `doom_loop` | `permission.special.doom_loop` | Medium | Same idea, different location. |
|
|
58
|
+
| `skill` permission rules | `permission.skills` | Medium | Same purpose, but Pi uses a dedicated plural `skills` section. |
|
|
59
|
+
| MCP-related access | `permission.mcp` for proxy targets, `permission.tools` for direct registered tools | Medium | This is the biggest Pi-specific difference: proxy MCP targets and direct tool names are intentionally split. |
|
|
60
|
+
| OpenCode-specific permissions like `webfetch`, `websearch`, `question`, `lsp`, `todowrite` | Usually extension-specific Pi tool names under `permission.tools` | Low-Medium | These do not have universal built-in one-to-one Pi names; map them to the actual registered tools available in your Pi setup. |
|
|
61
|
+
|
|
62
|
+
### Most important difference
|
|
63
|
+
|
|
64
|
+
In OpenCode, many permission names live in one broad permission namespace. In Pi with this extension, there is a deliberate split:
|
|
65
|
+
|
|
66
|
+
| Use this when... | Put the rule here |
|
|
67
|
+
|---|---|
|
|
68
|
+
| You are targeting the registered **`mcp` proxy tool** and its internal server/tool targets | `permission.mcp` |
|
|
69
|
+
| You are targeting an actual registered tool name, including direct extension tools like `context7_*`, `github_*`, or `exa_*` | `permission.tools` |
|
|
70
|
+
|
|
71
|
+
### Fast porting guide
|
|
72
|
+
|
|
73
|
+
| If your OpenCode agent has... | In Pi, do this |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `permission.bash` rules | Move them into `permission.bash` |
|
|
76
|
+
| `permission.external_directory` | Move it to `permission.special.external_directory` |
|
|
77
|
+
| `permission.doom_loop` | Move it to `permission.special.doom_loop` |
|
|
78
|
+
| `permission.skill` rules | Move them to `permission.skills` |
|
|
79
|
+
| Tool-ish permissions like `read`, `grep`, `list`, `task`, or third-party tool names | Put them in `permission.tools` |
|
|
80
|
+
| MCP server/tool target logic | Put proxy-target rules in `permission.mcp` |
|
|
81
|
+
|
|
82
|
+
### Practical takeaway
|
|
83
|
+
|
|
84
|
+
If you are coming from OpenCode, you usually do **not** need to rewrite your whole agent. In most cases, porting is just:
|
|
85
|
+
|
|
86
|
+
1. Keep the agent markdown/frontmatter structure.
|
|
87
|
+
2. Move OpenCode-style tool permissions into Pi's `tools` section.
|
|
88
|
+
3. Move `external_directory` and `doom_loop` into `special`.
|
|
89
|
+
4. Split MCP proxy target rules into `mcp` and direct registered tool rules into `tools`.
|
|
9
90
|
|
|
10
91
|
## Features
|
|
11
92
|
|
|
@@ -17,6 +98,7 @@ Permission enforcement extension for the Pi coding agent that provides centraliz
|
|
|
17
98
|
- **Skill Protection** — Controls which skills can be loaded or read from disk, including multi-block prompt sanitization
|
|
18
99
|
- **Per-Agent Overrides** — Agent-specific permission policies via YAML frontmatter
|
|
19
100
|
- **Subagent Permission Forwarding** — Forwards `ask` confirmations from non-UI subagents back to the main interactive session
|
|
101
|
+
- **Runtime YOLO Control** — Lets users toggle yolo mode from the settings modal and lets other extensions toggle it through the runtime API
|
|
20
102
|
- **File-Based Review Logging** — Writes permission request/denial review entries to a file by default for later auditing
|
|
21
103
|
- **Optional Debug Logging** — Keeps verbose extension diagnostics in a separate file when enabled in `config.json`
|
|
22
104
|
- **JSON Schema Validation** — Full schema for editor autocomplete and config validation
|
|
@@ -41,7 +123,7 @@ Place this folder in one of the following locations:
|
|
|
41
123
|
|
|
42
124
|
Pi auto-discovers extensions in these paths.
|
|
43
125
|
|
|
44
|
-
> **Tip:** All `~/.pi/agent` paths shown in this document are defaults. If the `PI_CODING_AGENT_DIR` environment variable is set, pi uses that directory instead. The extension automatically follows pi's `getAgentDir()` helper
|
|
126
|
+
> **Tip:** All `~/.pi/agent` paths shown in this document are defaults. If the `PI_CODING_AGENT_DIR` environment variable is set, pi uses that directory instead. The extension automatically follows pi's `getAgentDir()` helper for extension installation, session directories, and extension-local config paths. If you need policy lookup to come from a different global agent root, set `PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR`.
|
|
45
127
|
|
|
46
128
|
## Usage
|
|
47
129
|
|
|
@@ -90,10 +172,10 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
90
172
|
**Additional behaviors:**
|
|
91
173
|
- Unknown/unregistered tools are blocked before permission checks (prevents bypass attempts)
|
|
92
174
|
- The `Available tools:` system prompt section is rewritten to match the filtered active tool set
|
|
93
|
-
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled
|
|
175
|
+
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled through the same registered-tool permission layer instead of private built-in hardcodes
|
|
94
176
|
- When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
|
|
95
177
|
- 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
|
|
178
|
+
- Permission review logs include requested bash command text plus redacted prompt/input metadata for auditing without writing raw prompts or generic tool payload previews
|
|
97
179
|
- 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`
|
|
98
180
|
|
|
99
181
|
## Configuration
|
|
@@ -102,7 +184,9 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
102
184
|
|
|
103
185
|
**Location:** global Pi extension config (default: `~/.pi/agent/extensions/pi-permission-system/config.json`, respects `PI_CODING_AGENT_DIR`)
|
|
104
186
|
|
|
105
|
-
|
|
187
|
+
Set `PI_PERMISSION_SYSTEM_CONFIG_PATH` to point this extension at a specific config file when the default global path is not appropriate.
|
|
188
|
+
|
|
189
|
+
The extension creates this file automatically when it is missing. It controls extension-local logging behavior and yolo mode defaults:
|
|
106
190
|
|
|
107
191
|
```json
|
|
108
192
|
{
|
|
@@ -118,18 +202,45 @@ The extension creates this file automatically when it is missing. It controls on
|
|
|
118
202
|
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
|
|
119
203
|
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
|
|
120
204
|
|
|
121
|
-
Both logs write to files only under the extension directory. No debug output is printed to the terminal.
|
|
205
|
+
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.
|
|
206
|
+
|
|
207
|
+
### Runtime YOLO Control
|
|
208
|
+
|
|
209
|
+
Use `/permission-system` to open the settings modal and inspect or change yolo mode interactively.
|
|
210
|
+
|
|
211
|
+
Other extensions can toggle yolo mode immediately through the shared runtime API:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
type PermissionSystemGlobal = typeof globalThis & {
|
|
215
|
+
__piPermissionSystem?: {
|
|
216
|
+
toggleYoloMode(options?: { persist?: boolean; source?: string }): { error?: string };
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
pi.registerShortcut("f8", {
|
|
221
|
+
description: "Toggle pi-permission-system YOLO mode",
|
|
222
|
+
handler: () => {
|
|
223
|
+
const permissionSystem = (globalThis as PermissionSystemGlobal).__piPermissionSystem;
|
|
224
|
+
const result = permissionSystem?.toggleYoloMode({ source: "my-extension" });
|
|
225
|
+
if (result?.error) {
|
|
226
|
+
// Notify or log the error in your extension.
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The runtime API exposes `getYoloMode()`, `setYoloMode(enabled, options?)`, and `toggleYoloMode(options?)`. Runtime updates persist to `config.json` by default; pass `{ persist: false }` for a current-session-only toggle.
|
|
122
233
|
|
|
123
234
|
### Global Policy File
|
|
124
235
|
|
|
125
|
-
**Location:** global Pi policy file (default: `~/.pi/agent/pi-permissions.jsonc`, respects `PI_CODING_AGENT_DIR`)
|
|
236
|
+
**Location:** global Pi policy file (default: `~/.pi/agent/pi-permissions.jsonc`, respects `PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR` when set and otherwise follows `PI_CODING_AGENT_DIR`)
|
|
126
237
|
|
|
127
238
|
The policy file is a JSON object with these sections:
|
|
128
239
|
|
|
129
240
|
| Section | Description |
|
|
130
241
|
|-----------------|-----------------------------------------------------|
|
|
131
242
|
| `defaultPolicy` | Fallback permissions per category |
|
|
132
|
-
| `tools` |
|
|
243
|
+
| `tools` | Pattern-based tool permissions for registered tools |
|
|
133
244
|
| `bash` | Command pattern permissions |
|
|
134
245
|
| `mcp` | MCP server/tool permissions for calls routed through a registered `mcp` tool |
|
|
135
246
|
| `skills` | Skill name pattern permissions |
|
|
@@ -139,7 +250,7 @@ The policy file is a JSON object with these sections:
|
|
|
139
250
|
|
|
140
251
|
### Global Per-Agent Overrides
|
|
141
252
|
|
|
142
|
-
Override global permissions for specific agents via YAML frontmatter in the global Pi agents directory (default: `~/.pi/agent/agents/<agent>.md`, respects `PI_CODING_AGENT_DIR`):
|
|
253
|
+
Override global permissions for specific agents via YAML frontmatter in the global Pi agents directory (default: `~/.pi/agent/agents/<agent-name>.md`, respects `PI_PERMISSION_SYSTEM_POLICY_AGENT_DIR` when set and otherwise follows `PI_CODING_AGENT_DIR`):
|
|
143
254
|
|
|
144
255
|
```yaml
|
|
145
256
|
---
|
|
@@ -162,7 +273,7 @@ permission:
|
|
|
162
273
|
|
|
163
274
|
**MCP behavior:** `permission.tools.mcp` is the coarse entry/fallback permission for a registered `mcp` tool when one is available. More specific `permission.mcp` target rules override that fallback when they match.
|
|
164
275
|
|
|
165
|
-
**Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
|
|
276
|
+
**Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors. If you are porting from OpenCode, simplify richer YAML frontmatter before expecting a clean migration.
|
|
166
277
|
|
|
167
278
|
### Project-Level Policy Files
|
|
168
279
|
|
|
@@ -171,7 +282,7 @@ The extension can also layer project-local permission files relative to the acti
|
|
|
171
282
|
| Scope | Path |
|
|
172
283
|
|-------|------|
|
|
173
284
|
| Project policy | `<cwd>/.pi/agent/pi-permissions.jsonc` |
|
|
174
|
-
| Project agent override | `<cwd>/.pi/agent/agents/<agent>.md` |
|
|
285
|
+
| Project agent override | `<cwd>/.pi/agent/agents/<agent-name>.md` |
|
|
175
286
|
|
|
176
287
|
Project-local files use the same formats as the global policy file and global agent frontmatter. These project files are resolved from Pi's current session `cwd`, so they are workspace-specific and do **not** move under `PI_CODING_AGENT_DIR`.
|
|
177
288
|
|
|
@@ -181,7 +292,7 @@ Project-local files use the same formats as the global policy file and global ag
|
|
|
181
292
|
3. Global agent frontmatter
|
|
182
293
|
4. Project agent frontmatter
|
|
183
294
|
|
|
184
|
-
Later layers override earlier layers within the same permission category. For wildcard-based sections like `bash`, `mcp`, `skills`, and `special`, matching still follows
|
|
295
|
+
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 `tools`, `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.
|
|
185
296
|
|
|
186
297
|
---
|
|
187
298
|
|
|
@@ -205,7 +316,7 @@ Sets fallback permissions when no specific rule matches:
|
|
|
205
316
|
|
|
206
317
|
### `tools`
|
|
207
318
|
|
|
208
|
-
Controls tools by
|
|
319
|
+
Controls tools by registered name pattern. This is the recommended standalone format for **all** tool entries, including Pi built-ins and arbitrary third-party extension tools. Patterns use `*` wildcards and follow last-declared-match semantics, so put broad fallbacks first and specific overrides later.
|
|
209
320
|
|
|
210
321
|
| Tool name example | Description |
|
|
211
322
|
|-----------------------|-------------|
|
|
@@ -214,29 +325,33 @@ Controls tools by exact registered name (no wildcards). This is the recommended
|
|
|
214
325
|
| `mcp` | Registered MCP proxy tool entry/fallback when available |
|
|
215
326
|
| `task` | Delegation tool handled like any other registered extension tool |
|
|
216
327
|
| `third_party_tool` | Arbitrary registered extension tool |
|
|
328
|
+
| `context7_*` | Wildcard for direct tools registered by another extension |
|
|
329
|
+
| `*` | Fallback for every registered tool not matched by a later rule |
|
|
217
330
|
|
|
218
331
|
```jsonc
|
|
219
332
|
{
|
|
220
333
|
"tools": {
|
|
221
|
-
"
|
|
222
|
-
"
|
|
334
|
+
"*": "ask",
|
|
335
|
+
"context7_*": "ask",
|
|
336
|
+
"third_party_tool": "ask",
|
|
223
337
|
"mcp": "allow",
|
|
224
|
-
"
|
|
338
|
+
"read": "allow",
|
|
339
|
+
"write": "deny"
|
|
225
340
|
}
|
|
226
341
|
}
|
|
227
342
|
```
|
|
228
343
|
|
|
229
|
-
Unknown or absent tools are not required in the config. If another extension is not installed, its tool simply will not be registered at runtime, and this extension will block attempts to call that missing tool before permission checks run.
|
|
344
|
+
Unknown or absent tools are not required in the config. If another extension is not installed, its tool simply will not be registered at runtime, and this extension will block attempts to call that missing tool before permission checks run. Wildcard `tools` rules apply to direct tools from any extension; no adapter-specific naming is required.
|
|
230
345
|
|
|
231
346
|
> **Note:** Setting `tools.bash` affects the *default* for bash commands, but `bash` patterns can provide command-level overrides.
|
|
232
347
|
>
|
|
233
|
-
> **Note:** Setting `tools.mcp` controls coarse access to a registered `mcp` tool when one is available. Specific `mcp` rules still override it when a target pattern matches.
|
|
348
|
+
> **Note:** Setting `tools.mcp` controls coarse access to a registered `mcp` proxy tool when one is available. Specific `mcp` rules still override it when a proxy target pattern matches. Direct MCP tools registered by extensions are regular registered tools and should be controlled with `tools` patterns such as `context7_*` or `github_*`.
|
|
234
349
|
>
|
|
235
350
|
> **Note:** Top-level shorthand is only supported for the canonical Pi built-ins (`bash`, `read`, `write`, `edit`, `grep`, `find`, `ls`) in agent frontmatter. Use `permission.tools.<name>` for `mcp`, `task`, and any third-party tool.
|
|
236
351
|
|
|
237
352
|
### `bash`
|
|
238
353
|
|
|
239
|
-
Command patterns use `*` wildcards and match against the full command string. If multiple patterns match, the **last matching rule wins**.
|
|
354
|
+
Command patterns use `*` wildcards and match against the full command string. If multiple patterns match, the **last declared matching rule wins**. Put broad fallback rules first and more specific overrides later.
|
|
240
355
|
|
|
241
356
|
```jsonc
|
|
242
357
|
{
|
|
@@ -262,9 +377,10 @@ MCP permissions match against derived targets from tool input. These rules are m
|
|
|
262
377
|
```jsonc
|
|
263
378
|
{
|
|
264
379
|
"mcp": {
|
|
380
|
+
"*": "ask",
|
|
381
|
+
"myServer:*": "ask",
|
|
265
382
|
"mcp_status": "allow",
|
|
266
383
|
"mcp_list": "allow",
|
|
267
|
-
"myServer:*": "ask",
|
|
268
384
|
"dangerousServer": "deny"
|
|
269
385
|
}
|
|
270
386
|
}
|
|
@@ -322,7 +438,6 @@ Reserved permission checks:
|
|
|
322
438
|
|----------------------|------------------------------------------|
|
|
323
439
|
| `doom_loop` | Controls doom loop detection behavior |
|
|
324
440
|
| `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 |
|
|
325
|
-
| `tool_call_limit` | *(schema only, not enforced yet)* |
|
|
326
441
|
|
|
327
442
|
```jsonc
|
|
328
443
|
{
|
|
@@ -361,10 +476,10 @@ Reserved permission checks:
|
|
|
361
476
|
{
|
|
362
477
|
"defaultPolicy": { "tools": "ask", "bash": "deny", "mcp": "ask", "skills": "ask", "special": "ask" },
|
|
363
478
|
"bash": {
|
|
479
|
+
"git *": "ask",
|
|
364
480
|
"git status": "allow",
|
|
365
481
|
"git diff": "allow",
|
|
366
|
-
"git log *": "allow"
|
|
367
|
-
"git *": "ask"
|
|
482
|
+
"git log *": "allow"
|
|
368
483
|
}
|
|
369
484
|
}
|
|
370
485
|
```
|
|
@@ -375,11 +490,11 @@ Reserved permission checks:
|
|
|
375
490
|
{
|
|
376
491
|
"defaultPolicy": { "tools": "ask", "bash": "ask", "mcp": "ask", "skills": "ask", "special": "ask" },
|
|
377
492
|
"mcp": {
|
|
493
|
+
"*": "ask",
|
|
378
494
|
"mcp_status": "allow",
|
|
379
495
|
"mcp_list": "allow",
|
|
380
496
|
"mcp_search": "allow",
|
|
381
|
-
"mcp_describe": "allow"
|
|
382
|
-
"*": "ask"
|
|
497
|
+
"mcp_describe": "allow"
|
|
383
498
|
}
|
|
384
499
|
}
|
|
385
500
|
```
|
|
@@ -431,34 +546,45 @@ When the extension prompts, denies, or forwards permission requests, it can appe
|
|
|
431
546
|
```text
|
|
432
547
|
Default global logs directory: ~/.pi/agent/extensions/pi-permission-system/logs/
|
|
433
548
|
Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs when PI_CODING_AGENT_DIR is set
|
|
549
|
+
Override logs directory: $PI_PERMISSION_SYSTEM_LOGS_DIR when set
|
|
434
550
|
```
|
|
435
551
|
|
|
436
|
-
- `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history, including
|
|
552
|
+
- `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
|
|
437
553
|
- `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
|
|
438
554
|
|
|
439
555
|
### Architecture
|
|
440
556
|
|
|
441
557
|
```
|
|
442
|
-
index.ts
|
|
558
|
+
index.ts → Root Pi entrypoint shim
|
|
443
559
|
src/
|
|
444
|
-
├── index.ts
|
|
445
|
-
├──
|
|
446
|
-
├──
|
|
447
|
-
├──
|
|
448
|
-
├──
|
|
449
|
-
├──
|
|
450
|
-
├──
|
|
451
|
-
├──
|
|
452
|
-
├──
|
|
453
|
-
|
|
560
|
+
├── index.ts → Extension bootstrap, permission checks, readable prompts, review logging, reload handling, and subagent forwarding
|
|
561
|
+
├── before-agent-start-cache.ts → Caches prompt/tool filtering state between before_agent_start runs
|
|
562
|
+
├── bash-filter.ts → Bash command wildcard pattern matching
|
|
563
|
+
├── common.ts → Shared utilities (YAML parsing, type guards, etc.)
|
|
564
|
+
├── config-modal.ts → `/permission-system` modal registration and settings UI wiring
|
|
565
|
+
├── extension-config.ts → Extension-local config loading and default creation
|
|
566
|
+
├── logging.ts → File-only debug/review logging helpers
|
|
567
|
+
├── model-option-compatibility.ts → Guards unsupported provider/model options
|
|
568
|
+
├── permission-dialog.ts → Interactive permission approval UI helpers
|
|
569
|
+
├── permission-forwarding.ts → Subagent-to-parent permission forwarding utilities
|
|
570
|
+
├── permission-manager.ts → Global/project policy loading, merging, and resolution with caching
|
|
571
|
+
├── skill-prompt-sanitizer.ts → Skill prompt parsing, multi-block sanitization, and skill-read path matching
|
|
572
|
+
├── status.ts → Status line integration for runtime yolo state
|
|
573
|
+
├── system-prompt-sanitizer.ts → Available-tools prompt filtering helpers
|
|
574
|
+
├── tool-registry.ts → Registered tool name resolution
|
|
575
|
+
├── types.ts → TypeScript type definitions
|
|
576
|
+
├── wildcard-matcher.ts → Shared wildcard pattern compilation and matching
|
|
577
|
+
├── yolo-mode.ts → Runtime yolo approval helpers
|
|
578
|
+
├── yolo-mode-api.ts → Shared global runtime API for yolo toggling
|
|
579
|
+
└── zellij-modal.ts → Reusable modal/settings UI components
|
|
454
580
|
tests/
|
|
455
|
-
├── permission-system.test.ts
|
|
456
|
-
├── config-modal.test.ts
|
|
457
|
-
└── test-harness.ts
|
|
581
|
+
├── permission-system.test.ts → Core permission, layering, forwarding, and policy tests
|
|
582
|
+
├── config-modal.test.ts → Modal command behavior tests
|
|
583
|
+
└── test-harness.ts → Shared lightweight test helpers
|
|
458
584
|
schemas/
|
|
459
|
-
└── permissions.schema.json
|
|
585
|
+
└── permissions.schema.json → JSON Schema for policy validation
|
|
460
586
|
config/
|
|
461
|
-
└── config.example.json
|
|
587
|
+
└── config.example.json → Starter global policy template
|
|
462
588
|
```
|
|
463
589
|
|
|
464
590
|
#### Module Organization
|
|
@@ -522,11 +648,14 @@ npx --yes ajv-cli@5 validate \
|
|
|
522
648
|
|
|
523
649
|
## Development
|
|
524
650
|
|
|
651
|
+
Runtime checks require Node.js 20+; the test suite requires Bun 1.1+.
|
|
652
|
+
|
|
525
653
|
```bash
|
|
526
|
-
npm run build
|
|
527
|
-
npm run lint
|
|
528
|
-
npm run
|
|
529
|
-
npm run
|
|
654
|
+
npm run build # Run TypeScript type checks
|
|
655
|
+
npm run lint # Run local static checks
|
|
656
|
+
npm run validate:artifacts # Validate JSON/schema/example artifacts
|
|
657
|
+
npm run test # Run Bun tests from ./tests
|
|
658
|
+
npm run check # Run static, artifact, and test checks
|
|
530
659
|
```
|
|
531
660
|
|
|
532
661
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-permission-system",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"index.ts",
|
|
12
12
|
"src",
|
|
13
13
|
"tests",
|
|
14
|
-
"config.json",
|
|
15
14
|
"config/config.example.json",
|
|
16
15
|
"schemas/permissions.schema.json",
|
|
17
16
|
"README.md",
|
|
@@ -19,10 +18,12 @@
|
|
|
19
18
|
"LICENSE"
|
|
20
19
|
],
|
|
21
20
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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",
|
|
24
25
|
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
25
|
-
"check": "npm run lint && npm run test"
|
|
26
|
+
"check": "npm run lint && npm run validate:artifacts && npm run test"
|
|
26
27
|
},
|
|
27
28
|
"keywords": [
|
|
28
29
|
"pi-package",
|
|
@@ -47,7 +48,8 @@
|
|
|
47
48
|
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
|
-
"node": ">=20"
|
|
51
|
+
"node": ">=20",
|
|
52
|
+
"bun": ">=1.1.0"
|
|
51
53
|
},
|
|
52
54
|
"publishConfig": {
|
|
53
55
|
"access": "public"
|
|
@@ -58,9 +60,9 @@
|
|
|
58
60
|
]
|
|
59
61
|
},
|
|
60
62
|
"peerDependencies": {
|
|
61
|
-
"@mariozechner/pi-ai": "^0.
|
|
62
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
63
|
-
"@mariozechner/pi-tui": "^0.
|
|
63
|
+
"@mariozechner/pi-ai": "^0.72.0",
|
|
64
|
+
"@mariozechner/pi-coding-agent": "^0.72.0",
|
|
65
|
+
"@mariozechner/pi-tui": "^0.72.0",
|
|
64
66
|
"@sinclair/typebox": "^0.34.49"
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"tools": {
|
|
34
|
-
"description": "
|
|
34
|
+
"description": "Pattern-based permissions for registered tools. Use exact names or `*` wildcards for canonical Pi built-ins and any extension-provided or third-party tools.",
|
|
35
35
|
"$ref": "#/$defs/permissionMap"
|
|
36
36
|
},
|
|
37
37
|
"bash": {
|
|
@@ -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> {
|