@gotgenes/pi-permission-system 5.2.1 → 5.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 +33 -0
- package/README.md +40 -702
- package/package.json +1 -1
- package/src/handlers/input.ts +31 -4
- package/src/handlers/lifecycle.ts +1 -0
- package/src/handlers/tool-call.ts +135 -9
- package/src/handlers/types.ts +5 -0
- package/src/index.ts +17 -0
- package/src/permission-dialog.ts +6 -0
- package/src/permission-event-rpc.ts +229 -0
- package/src/permission-events.ts +159 -0
- package/src/permission-prompter.ts +1 -1
- package/tests/handlers/before-agent-start.test.ts +2 -0
- package/tests/handlers/input-events.test.ts +226 -0
- package/tests/handlers/input.test.ts +2 -0
- package/tests/handlers/lifecycle.test.ts +8 -0
- package/tests/handlers/tool-call-events.test.ts +389 -0
- package/tests/handlers/tool-call.test.ts +2 -0
- package/tests/permission-event-rpc.test.ts +499 -0
- package/tests/permission-events.test.ts +299 -0
- package/tests/permission-prompter.test.ts +5 -1
- package/tests/permission-system.test.ts +1 -0
- package/tests/session-start.test.ts +2 -0
package/README.md
CHANGED
|
@@ -2,732 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@gotgenes/pi-permission-system) [](https://github.com/gotgenes/pi-permission-system/actions/workflows/ci.yml) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](https://pi.mariozechner.at/)
|
|
4
4
|
|
|
5
|
-
Permission enforcement extension for the Pi coding agent that provides centralized, deterministic permission gates
|
|
5
|
+
Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) coding agent that provides centralized, deterministic permission gates over tool, bash, MCP, skill, and special operations.
|
|
6
6
|
|
|
7
7
|
> **Fork notice:** This package is a full fork of [MasuRii/pi-permission-system](https://github.com/MasuRii/pi-permission-system), published to npm as `@gotgenes/pi-permission-system`.
|
|
8
8
|
> It has diverged substantially from upstream in config format, internal architecture, and permission model.
|
|
9
|
-
> The `/permission-system` slash command name is the only upstream identity preserved.
|
|
10
9
|
|
|
11
|
-
##
|
|
10
|
+
## What It Does
|
|
12
11
|
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **Per-Agent Overrides** — Agent-specific permission policies via YAML frontmatter
|
|
20
|
-
- **Subagent Permission Forwarding** — Forwards `ask` confirmations from non-UI subagents back to the main interactive session
|
|
21
|
-
- **File-Based Review Logging** — Writes permission request/denial review entries to a file by default for later auditing
|
|
22
|
-
- **Optional Debug Logging** — Keeps verbose extension diagnostics in a separate file when enabled in `config.json`
|
|
23
|
-
- **JSON Schema Validation** — Full schema for editor autocomplete and config validation
|
|
24
|
-
- **External Directory Guard** — Enforces `special.external_directory` for path-bearing file tools and bash commands that reference paths outside the active working directory
|
|
12
|
+
- **Hides disallowed tools** before the agent starts — no wasted turns probing for blocked tools
|
|
13
|
+
- **Enforces allow / ask / deny** at tool-call time with UI confirmation dialogs
|
|
14
|
+
- **Controls bash commands** with wildcard pattern matching (`git *: ask`, `rm -rf *: deny`)
|
|
15
|
+
- **Gates MCP and skill access** at server, tool, and skill-name granularity
|
|
16
|
+
- **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
|
|
17
|
+
- **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
|
|
25
18
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
### npm package
|
|
19
|
+
## Install
|
|
29
20
|
|
|
30
21
|
```bash
|
|
31
|
-
pi install npm
|
|
22
|
+
pi install npm:@gotgenes/pi-permission-system
|
|
32
23
|
```
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Place this folder in one of the following locations:
|
|
37
|
-
|
|
38
|
-
| Scope | Path |
|
|
39
|
-
| -------------- | ------------------------------------------------------------------------------ |
|
|
40
|
-
| Global default | `~/.pi/agent/extensions/pi-permission-system` (respects `PI_CODING_AGENT_DIR`) |
|
|
41
|
-
| Project | `.pi/extensions/pi-permission-system` |
|
|
42
|
-
|
|
43
|
-
Pi auto-discovers extensions in these paths.
|
|
25
|
+
## Quick Start
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Usage
|
|
48
|
-
|
|
49
|
-
### Quick Start
|
|
50
|
-
|
|
51
|
-
1. Create the global config file (default: `~/.pi/agent/extensions/pi-permission-system/config.json`, respects `PI_CODING_AGENT_DIR`):
|
|
27
|
+
1. Create the global config file at `~/.pi/agent/extensions/pi-permission-system/config.json`:
|
|
52
28
|
|
|
53
29
|
```jsonc
|
|
54
30
|
{
|
|
55
31
|
"permission": {
|
|
56
32
|
"*": "ask",
|
|
57
33
|
"read": "allow",
|
|
58
|
-
"write": "deny"
|
|
34
|
+
"write": "deny",
|
|
35
|
+
"bash": { "git status": "allow", "git *": "ask" }
|
|
59
36
|
}
|
|
60
37
|
}
|
|
61
38
|
```
|
|
62
39
|
|
|
63
40
|
1. Start Pi — the extension automatically loads and enforces your policy.
|
|
64
41
|
|
|
65
|
-
### Permission States
|
|
66
|
-
|
|
67
42
|
All permissions use one of three states:
|
|
68
43
|
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### Pi Integration Hooks
|
|
76
|
-
|
|
77
|
-
The extension integrates via Pi's lifecycle hooks:
|
|
78
|
-
|
|
79
|
-
| Hook | Behavior |
|
|
80
|
-
| -------------------- | ------------------------------------------------------------------------------------------------- |
|
|
81
|
-
| `before_agent_start` | Filters active tools, removes denied tool entries from the system prompt, and hides denied skills |
|
|
82
|
-
| `tool_call` | Enforces permissions for every tool invocation |
|
|
83
|
-
| `input` | Intercepts `/skill:<name>` requests and enforces skill policy |
|
|
44
|
+
|State|Behavior|
|
|
45
|
+
|---|---|
|
|
46
|
+
|`allow`|Permits the action silently|
|
|
47
|
+
|`deny`|Blocks the action with an error message|
|
|
48
|
+
|`ask`|Prompts the user for confirmation via UI|
|
|
84
49
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- Unknown/unregistered tools are blocked before permission checks (prevents bypass attempts)
|
|
88
|
-
- The `Available tools:` system prompt section is rewritten to match the filtered active tool set
|
|
89
|
-
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled by exact registered name instead of private built-in hardcodes
|
|
90
|
-
- When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
|
|
91
|
-
- Generic extension-tool approval prompts include a bounded input preview; built-in file tools use concise human-readable summaries instead of raw multiline JSON
|
|
92
|
-
- Permission review logs include bounded `toolInputPreview` values for non-bash/non-MCP tool calls so approvals can be audited without writing raw full payloads
|
|
93
|
-
- Path-bearing file tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) evaluate `permission.external_directory` before their normal tool permission when an explicit path points outside `ctx.cwd`
|
|
94
|
-
- Bash commands are parsed with a full bash AST (`web-tree-sitter` + `tree-sitter-bash`) to extract path-bearing arguments; only genuine command arguments and redirect destinations are checked — heredoc bodies, comments, and quoted string contents are correctly excluded — and paths that resolve outside `ctx.cwd` trigger the same `permission.external_directory` gate before the normal bash pattern check
|
|
50
|
+
When the dialog prompts, you can approve once or approve a pattern for the rest of the session.
|
|
51
|
+
See [docs/session-approvals.md](docs/session-approvals.md) for details on session-scoped rules and pattern suggestions.
|
|
95
52
|
|
|
96
53
|
## Configuration
|
|
97
54
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
**Location:** one unified config file per scope, following the `pi-autoformat` convention:
|
|
101
|
-
|
|
102
|
-
| Scope | Path |
|
|
103
|
-
| ------- | ------------------------------------------------------------------------------------------------- |
|
|
104
|
-
| Global | `~/.pi/agent/extensions/pi-permission-system/config.json` (respects `PI_CODING_AGENT_DIR`) |
|
|
105
|
-
| Project | `<cwd>/.pi/extensions/pi-permission-system/config.json` |
|
|
106
|
-
|
|
107
|
-
Project config overrides global config; per-agent frontmatter overrides both.
|
|
108
|
-
The `permission` object uses deep-shallow merge: string-vs-string replaces; both-object shallow-merges pattern maps; string-vs-object the override wins entirely.
|
|
109
|
-
Scalar fields (`debugLog`, `permissionReviewLog`, `yoloMode`) use simple replacement.
|
|
110
|
-
|
|
111
|
-
The config file combines runtime knobs and permission policy in one object:
|
|
112
|
-
|
|
113
|
-
```jsonc
|
|
114
|
-
{
|
|
115
|
-
"$schema": "https://raw.githubusercontent.com/gotgenes/pi-permission-system/main/schemas/permissions.schema.json",
|
|
116
|
-
|
|
117
|
-
// Runtime knobs
|
|
118
|
-
"debugLog": false,
|
|
119
|
-
"permissionReviewLog": true,
|
|
120
|
-
"yoloMode": false,
|
|
121
|
-
"piInfrastructureReadPaths": [], // extra dirs to auto-allow for reads
|
|
122
|
-
|
|
123
|
-
// Flat permission policy
|
|
124
|
-
"permission": {
|
|
125
|
-
"*": "ask", // universal fallback
|
|
126
|
-
"read": "allow",
|
|
127
|
-
"write": "deny",
|
|
128
|
-
"bash": { "git status": "allow", "git *": "ask" },
|
|
129
|
-
"mcp": { "mcp_status": "allow" },
|
|
130
|
-
"skill": { "*": "ask" },
|
|
131
|
-
"external_directory": "ask"
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
#### Runtime knobs
|
|
137
|
-
|
|
138
|
-
| Key | Default | Description |
|
|
139
|
-
| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
|
|
140
|
-
| `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl` |
|
|
141
|
-
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
|
|
142
|
-
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
|
|
143
|
-
| `piInfrastructureReadPaths` | `[]` | Extra directories to auto-allow for reads, bypassing the `external_directory` gate (supports `~`) |
|
|
144
|
-
|
|
145
|
-
Both logs write to `~/.pi/agent/extensions/pi-permission-system/logs/`.
|
|
146
|
-
No debug output is printed to the terminal.
|
|
147
|
-
|
|
148
|
-
#### Policy sections
|
|
149
|
-
|
|
150
|
-
The config file is a JSON object with these policy sections:
|
|
151
|
-
|
|
152
|
-
The `permission` object maps surface names to actions:
|
|
153
|
-
|
|
154
|
-
| Key | Value type | Description |
|
|
155
|
-
| --- | ---------- | ----------- |
|
|
156
|
-
| `"*"` | string | Universal fallback — applies when no surface-specific rule matches |
|
|
157
|
-
| tool name (e.g. `read`) | string | Catch-all for that tool surface |
|
|
158
|
-
| `bash` | string or object | Bash catch-all or `{ pattern: action }` map |
|
|
159
|
-
| `mcp` | string or object | MCP catch-all or `{ pattern: action }` map |
|
|
160
|
-
| `skill` | string or object | Skill catch-all or `{ pattern: action }` map |
|
|
161
|
-
| `external_directory` | string or object | Controls access to paths outside `cwd`; supports `~/` and `$HOME/` patterns |
|
|
162
|
-
|
|
163
|
-
> **Note:** Trailing commas are **not** supported. If parsing fails, the extension falls back to `ask` for all categories.
|
|
164
|
-
|
|
165
|
-
### Global Per-Agent Overrides
|
|
166
|
-
|
|
167
|
-
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`):
|
|
168
|
-
|
|
169
|
-
```yaml
|
|
170
|
-
---
|
|
171
|
-
name: my-agent
|
|
172
|
-
permission:
|
|
173
|
-
read: allow
|
|
174
|
-
write: deny
|
|
175
|
-
mcp: allow
|
|
176
|
-
bash:
|
|
177
|
-
git status: allow
|
|
178
|
-
git *: ask
|
|
179
|
-
mcp:
|
|
180
|
-
chrome_devtools_*: deny
|
|
181
|
-
exa_*: allow
|
|
182
|
-
skill:
|
|
183
|
-
"*": ask
|
|
184
|
-
---
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
**MCP behavior:** `permission.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.
|
|
188
|
-
|
|
189
|
-
**Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
|
|
190
|
-
|
|
191
|
-
### Project-Level Config and Overrides
|
|
192
|
-
|
|
193
|
-
Project-local config uses the same format as the global config file.
|
|
194
|
-
Per-agent overrides use YAML frontmatter in the project agents directory:
|
|
195
|
-
|
|
196
|
-
| Scope | Path |
|
|
197
|
-
| ---------------------- | ------------------------------------------------------------- |
|
|
198
|
-
| Project config | `<cwd>/.pi/extensions/pi-permission-system/config.json` |
|
|
199
|
-
| Project agent override | `<cwd>/.pi/agent/agents/<agent>.md` |
|
|
200
|
-
|
|
201
|
-
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`.
|
|
202
|
-
|
|
203
|
-
**Precedence order:**
|
|
204
|
-
|
|
205
|
-
1. Global config file
|
|
206
|
-
2. Project config file
|
|
207
|
-
3. Global agent frontmatter
|
|
208
|
-
4. Project agent frontmatter
|
|
209
|
-
|
|
210
|
-
Later layers override earlier layers. Within a surface map like `bash` or `mcp`, matching follows **last matching rule wins** — put broad catch-alls first and specific overrides after. The recommended convention is also used by [OpenCode's permission model](https://opencode.ai/docs/permissions/#granular-rules-object-syntax).
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Policy Reference
|
|
215
|
-
|
|
216
|
-
### `permission["*"]` — universal fallback
|
|
217
|
-
|
|
218
|
-
The `"*"` key sets the action used when no surface-specific rule matches:
|
|
219
|
-
|
|
220
|
-
```jsonc
|
|
221
|
-
{
|
|
222
|
-
"permission": {
|
|
223
|
-
"*": "ask"
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Omitting `"*"` defaults to `"ask"` (least privilege).
|
|
229
|
-
|
|
230
|
-
### Tool surfaces
|
|
231
|
-
|
|
232
|
-
Any registered tool name can be a surface key. A string value is a catch-all for that surface.
|
|
55
|
+
Config lives in one JSON file per scope:
|
|
233
56
|
|
|
234
|
-
|
|
|
235
|
-
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
| `mcp` | Registered MCP proxy tool |
|
|
239
|
-
| `task` | Delegation tool |
|
|
240
|
-
| `third_party_tool` | Any other registered extension tool |
|
|
57
|
+
|Scope|Path|
|
|
58
|
+
|---|---|
|
|
59
|
+
|Global|`~/.pi/agent/extensions/pi-permission-system/config.json`|
|
|
60
|
+
|Project|`<cwd>/.pi/extensions/pi-permission-system/config.json`|
|
|
241
61
|
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
"permission": {
|
|
245
|
-
"read": "allow",
|
|
246
|
-
"write": "deny",
|
|
247
|
-
"third_party_tool": "ask"
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Unknown or absent tools are not required in the config. If a tool is not registered at runtime, this extension blocks it before permission checks run.
|
|
253
|
-
|
|
254
|
-
### `bash` surface
|
|
255
|
-
|
|
256
|
-
Command patterns use `*` wildcards matched against the full command string. **Last matching rule wins** — put broad catch-alls first, specific overrides after.
|
|
257
|
-
|
|
258
|
-
```jsonc
|
|
259
|
-
{
|
|
260
|
-
"permission": {
|
|
261
|
-
"bash": {
|
|
262
|
-
"*": "ask",
|
|
263
|
-
"git status": "allow",
|
|
264
|
-
"git diff": "allow",
|
|
265
|
-
"git *": "ask",
|
|
266
|
-
"rm -rf *": "deny"
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
String shorthand sets a catch-all for all bash commands:
|
|
273
|
-
|
|
274
|
-
```jsonc
|
|
275
|
-
{
|
|
276
|
-
"permission": { "bash": "allow" }
|
|
277
|
-
}
|
|
278
|
-
```
|
|
62
|
+
Project overrides global; per-agent YAML frontmatter overrides both.
|
|
279
63
|
|
|
280
|
-
|
|
64
|
+
Within a surface map like `bash` or `mcp`, **last matching rule wins** — put broad catch-alls first and specific overrides after.
|
|
281
65
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
| Target type | Examples |
|
|
285
|
-
| ----------- | -------- |
|
|
286
|
-
| Baseline ops | `mcp_status`, `mcp_list`, `mcp_search`, `mcp_describe`, `mcp_connect` |
|
|
287
|
-
| Server name | `myServer` |
|
|
288
|
-
| Server/tool combo | `myServer:search`, `myServer_search` |
|
|
289
|
-
| Generic | `mcp_call` |
|
|
290
|
-
|
|
291
|
-
```jsonc
|
|
292
|
-
{
|
|
293
|
-
"permission": {
|
|
294
|
-
"mcp": {
|
|
295
|
-
"*": "ask",
|
|
296
|
-
"mcp_status": "allow",
|
|
297
|
-
"mcp_list": "allow",
|
|
298
|
-
"myServer:*": "ask",
|
|
299
|
-
"dangerousServer": "deny"
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
```
|
|
66
|
+
For the full reference — all surfaces, runtime knobs, per-agent overrides, merge semantics, and common recipes — see [docs/configuration.md](docs/configuration.md).
|
|
304
67
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
String shorthand grants broad MCP access — useful for per-agent overrides:
|
|
308
|
-
|
|
309
|
-
```yaml
|
|
310
|
-
# ~/.pi/agent/agents/researcher.md (respects PI_CODING_AGENT_DIR)
|
|
311
|
-
---
|
|
312
|
-
name: researcher
|
|
313
|
-
permission:
|
|
314
|
-
mcp: allow
|
|
315
|
-
---
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### `skill` surface
|
|
319
|
-
|
|
320
|
-
Skill name patterns use `*` wildcards (note: surface is `skill`, not `skills`):
|
|
321
|
-
|
|
322
|
-
```jsonc
|
|
323
|
-
{
|
|
324
|
-
"permission": {
|
|
325
|
-
"skill": {
|
|
326
|
-
"*": "ask",
|
|
327
|
-
"dangerous-*": "deny",
|
|
328
|
-
"librarian": "allow"
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Home directory expansion in patterns
|
|
335
|
-
|
|
336
|
-
Pattern keys in any permission surface can start with `~/` or `$HOME/` (or be exactly `~` / `$HOME`).
|
|
337
|
-
They are expanded to the OS home directory at match time, so configs are portable across machines and users.
|
|
338
|
-
|
|
339
|
-
```jsonc
|
|
340
|
-
{
|
|
341
|
-
"permission": {
|
|
342
|
-
"external_directory": {
|
|
343
|
-
"*": "ask",
|
|
344
|
-
"~/development/*": "allow"
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
The pattern is stored and displayed as written (e.g. `~/development/*`) in logs and approval dialogs.
|
|
351
|
-
|
|
352
|
-
### `external_directory` surface
|
|
353
|
-
|
|
354
|
-
Controls access to paths outside the active working directory.
|
|
355
|
-
Use a pattern map to allow specific directories without opening all external access:
|
|
356
|
-
|
|
357
|
-
```jsonc
|
|
358
|
-
{
|
|
359
|
-
"permission": {
|
|
360
|
-
"external_directory": {
|
|
361
|
-
"*": "ask",
|
|
362
|
-
"~/development/*": "allow"
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
```
|
|
68
|
+
## Documentation
|
|
367
69
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
Infrastructure directories include:
|
|
378
|
-
|
|
379
|
-
1. The agent config directory (`~/.pi/agent/` or `$PI_CODING_AGENT_DIR`)
|
|
380
|
-
2. Git-cloned global packages (`<agentDir>/git/`)
|
|
381
|
-
3. The global `node_modules` root (auto-discovered from the extension's own install path; falls back to `npm root -g` when running from a local development checkout)
|
|
382
|
-
4. Project-local Pi packages (`<cwd>/.pi/npm/` and `<cwd>/.pi/git/`)
|
|
383
|
-
5. Any paths listed in `piInfrastructureReadPaths`
|
|
384
|
-
|
|
385
|
-
Write tools (`write`, `edit`) to infrastructure paths are **not** auto-allowed and still go through the gate.
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
## Common Recipes
|
|
390
|
-
|
|
391
|
-
### Read-Only Mode
|
|
392
|
-
|
|
393
|
-
```jsonc
|
|
394
|
-
{
|
|
395
|
-
"permission": {
|
|
396
|
-
"*": "ask",
|
|
397
|
-
"read": "allow",
|
|
398
|
-
"grep": "allow",
|
|
399
|
-
"find": "allow",
|
|
400
|
-
"ls": "allow",
|
|
401
|
-
"write": "deny",
|
|
402
|
-
"edit": "deny"
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Restricted Bash Surface
|
|
408
|
-
|
|
409
|
-
```jsonc
|
|
410
|
-
{
|
|
411
|
-
"permission": {
|
|
412
|
-
"*": "ask",
|
|
413
|
-
"bash": {
|
|
414
|
-
"*": "deny",
|
|
415
|
-
"git status": "allow",
|
|
416
|
-
"git diff": "allow",
|
|
417
|
-
"git log *": "allow"
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### MCP Discovery Only
|
|
424
|
-
|
|
425
|
-
```jsonc
|
|
426
|
-
{
|
|
427
|
-
"permission": {
|
|
428
|
-
"*": "ask",
|
|
429
|
-
"mcp": {
|
|
430
|
-
"*": "ask",
|
|
431
|
-
"mcp_status": "allow",
|
|
432
|
-
"mcp_list": "allow",
|
|
433
|
-
"mcp_search": "allow",
|
|
434
|
-
"mcp_describe": "allow"
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### Per-Agent Lockdown
|
|
441
|
-
|
|
442
|
-
In the global Pi agents directory (default: `~/.pi/agent/agents/reviewer.md`, respects `PI_CODING_AGENT_DIR`):
|
|
443
|
-
|
|
444
|
-
```yaml
|
|
445
|
-
---
|
|
446
|
-
permission:
|
|
447
|
-
write: deny
|
|
448
|
-
edit: deny
|
|
449
|
-
bash: deny
|
|
450
|
-
---
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
## Technical Details
|
|
456
|
-
|
|
457
|
-
### Permission Prompt Summaries
|
|
458
|
-
|
|
459
|
-
When a tool permission resolves to `ask`, the prompt is designed to be readable enough for an informed approval decision:
|
|
460
|
-
|
|
461
|
-
- `bash` prompts show the command and matched bash pattern when available.
|
|
462
|
-
- `mcp` prompts show the derived MCP target and matched rule when available.
|
|
463
|
-
- Built-in file tools show concise summaries, such as the target path and edit/write line counts, instead of raw multiline JSON.
|
|
464
|
-
- Unknown or third-party extension tools show a bounded single-line JSON preview of the input so users are not asked to approve a blind tool name.
|
|
465
|
-
|
|
466
|
-
Example edit approval prompt:
|
|
467
|
-
|
|
468
|
-
```text
|
|
469
|
-
Current agent requested tool 'edit' for '.gitignore' (1 replacement: edit #1 replaces 5 lines with 2 lines). Allow this call?
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### Session-Scoped Approvals
|
|
473
|
-
|
|
474
|
-
When any permission resolves to `ask`, the permission dialog offers four options:
|
|
475
|
-
|
|
476
|
-
```text
|
|
477
|
-
Yes | Yes, allow "<pattern>" for this session | No | No, provide reason
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
Selecting **Yes, allow "\<pattern\>" for this session** approves the current request and records the suggested wildcard pattern as a session rule.
|
|
481
|
-
Subsequent requests that match the pattern skip the prompt for the remainder of the session.
|
|
482
|
-
|
|
483
|
-
The suggested pattern is surface-specific:
|
|
484
|
-
|
|
485
|
-
|Surface|Example request|Suggested session pattern|
|
|
486
|
-
|---|---|---|
|
|
487
|
-
|bash|`git status --short`|`git status *`|
|
|
488
|
-
|mcp (qualified)|`exa:search`|`exa:*`|
|
|
489
|
-
|mcp (munged)|`exa_search`|`exa_*`|
|
|
490
|
-
|skill|`librarian`|`librarian`|
|
|
491
|
-
|tool (read, write, …)|`read`|`*`|
|
|
492
|
-
|external_directory|`/other/project/src/foo.ts`|`/other/project/src/*`|
|
|
493
|
-
|
|
494
|
-
#### Bash arity table
|
|
495
|
-
|
|
496
|
-
Bash pattern suggestions use a curated arity dictionary (`src/bash-arity.ts`) to determine how many tokens define the "human-understandable subcommand."
|
|
497
|
-
Longest matching prefix wins, so `npm run` (arity 3) takes precedence over `npm` (arity 2).
|
|
498
|
-
Unknown commands default to arity 1 (first word only).
|
|
499
|
-
|
|
500
|
-
|Example command|Arity entry matched|Suggested pattern|
|
|
501
|
-
|---|---|---|
|
|
502
|
-
|`git checkout main`|`git` → 2|`git checkout *`|
|
|
503
|
-
|`npm run dev`|`npm run` → 3|`npm run dev*`|
|
|
504
|
-
|`npm install lodash`|`npm` → 2|`npm install *`|
|
|
505
|
-
|`docker compose up`|`docker compose` → 3|`docker compose up *`|
|
|
506
|
-
|`rm -rf node_modules`|`rm` → 1|`rm *`|
|
|
507
|
-
|`mytool --verbose`|(unknown) → 1|`mytool *`|
|
|
508
|
-
|
|
509
|
-
The arity table covers common CLI tools including git, npm/pnpm/yarn/bun, docker, cargo, go, kubectl, gh, and others.
|
|
510
|
-
To add an entry, open `src/bash-arity.ts` and add a key/arity pair to the `ARITY` object.
|
|
511
|
-
Put the most specific multi-word prefix first (e.g. `"npm run": 3`) before the shorter fallback (`"npm": 2`).
|
|
512
|
-
|
|
513
|
-
Session approvals are ephemeral — they are never persisted to disk and are cleared on `session_shutdown`.
|
|
514
|
-
The review log records these decisions: `resolution: "approved_for_session"` when the user approves, and `resolution: "session_approved"` when a later request is matched by an existing session rule.
|
|
515
|
-
|
|
516
|
-
### Subagent Permission Forwarding
|
|
517
|
-
|
|
518
|
-
When a delegated or routed subagent runs without direct UI access, `ask` permissions can still be enforced by forwarding the confirmation request through Pi session directories. The main interactive session polls for forwarded requests, shows the confirmation prompt, writes the response, and the subagent resumes once that decision is available.
|
|
519
|
-
|
|
520
|
-
This keeps `ask` policies usable even when the original permission check happens inside a non-UI execution context.
|
|
521
|
-
|
|
522
|
-
### Coexistence with Subagent Extensions
|
|
523
|
-
|
|
524
|
-
Several pi-subagent extensions implement their own tool restriction mechanisms.
|
|
525
|
-
These compose correctly with the permission system because the two operate at different layers: **visibility** (subagent extension) and **policy** (permission system).
|
|
526
|
-
|
|
527
|
-
#### The two-layer model
|
|
528
|
-
|
|
529
|
-
```text
|
|
530
|
-
┌─────────────────────────────────────────────────────┐
|
|
531
|
-
│ Layer 1 – Visibility (subagent extension) │
|
|
532
|
-
│ Controls which tools are registered / active │
|
|
533
|
-
│ before the agent session starts. │
|
|
534
|
-
├─────────────────────────────────────────────────────┤
|
|
535
|
-
│ Layer 2 – Policy (pi-permission-system) │
|
|
536
|
-
│ Controls allow / ask / deny decisions on every │
|
|
537
|
-
│ tool call, bash command, MCP operation, etc. │
|
|
538
|
-
└─────────────────────────────────────────────────────┘
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
#### Known subagent extensions and their mechanisms
|
|
542
|
-
|
|
543
|
-
|Extension|Mechanism|Frontmatter key|
|
|
544
|
-
|----|----------|----------|
|
|
545
|
-
|[nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents)|`--tools` CLI allowlist passed to subprocess|`tools:` (CSV allowlist)|
|
|
546
|
-
|[tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents)|`session.setActiveToolsByName()` in-process filter|`disallowed_tools:` (CSV denylist)|
|
|
547
|
-
|[HazAT/pi-interactive-subagents](https://github.com/HazAT/pi-interactive-subagents)|`PI_DENY_TOOLS` env var + `--tools` CLI allowlist|`deny-tools:` (CSV denylist), `spawning:` (bool)|
|
|
548
|
-
|
|
549
|
-
#### Interaction rules
|
|
550
|
-
|
|
551
|
-
1. **Hidden tool → permission system never sees it.**
|
|
552
|
-
If a subagent extension removes a tool from the active set, the permission system receives no registration or call event for that tool.
|
|
553
|
-
The permission policy for that tool is irrelevant — it is already gone.
|
|
554
|
-
2. **Denied tool → hidden regardless of the subagent extension's allowlist.**
|
|
555
|
-
If the permission system denies a tool (via `deny` policy or tool filtering), it is removed from the active set before the agent starts.
|
|
556
|
-
A `tools:` allowlist in a subagent extension cannot restore a tool that the permission system has already hidden.
|
|
557
|
-
3. **The two denylist mechanisms are additive, not conflicting.**
|
|
558
|
-
A tool blocked by either layer stays blocked.
|
|
559
|
-
Neither layer can silently re-enable what the other has blocked.
|
|
560
|
-
|
|
561
|
-
#### `permission:` frontmatter is exclusive to this extension
|
|
562
|
-
|
|
563
|
-
The `permission:` key in an agent's YAML frontmatter is read exclusively by `pi-permission-system`.
|
|
564
|
-
It has no interaction with the `tools:`, `disallowed_tools:`, or `deny-tools:` keys consumed by subagent extensions.
|
|
565
|
-
You can freely use both in the same agent file:
|
|
566
|
-
|
|
567
|
-
```yaml
|
|
568
|
-
---
|
|
569
|
-
# Subagent extension: allow only bash and read_file in the subprocess
|
|
570
|
-
tools: bash,read_file
|
|
571
|
-
# pi-permission-system: still enforce ask on bash within those allowed tools
|
|
572
|
-
permission:
|
|
573
|
-
bash: ask
|
|
574
|
-
---
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
In this example the subagent extension restricts visibility to `bash` and `read_file`, and the permission system then gates every `bash` call with an `ask` prompt — both rules apply independently.
|
|
578
|
-
|
|
579
|
-
### Logging
|
|
580
|
-
|
|
581
|
-
When the extension prompts, denies, or forwards permission requests, it can append structured JSONL entries under:
|
|
582
|
-
|
|
583
|
-
```text
|
|
584
|
-
Default global logs directory: ~/.pi/agent/extensions/pi-permission-system/logs/
|
|
585
|
-
Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs/ when PI_CODING_AGENT_DIR is set
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
- `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
|
|
589
|
-
- `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
|
|
590
|
-
|
|
591
|
-
On every session start, the extension emits a `config.resolved` entry to both logs listing the resolved config paths and whether each exists.
|
|
592
|
-
This makes it easy to verify which files the extension actually loaded:
|
|
593
|
-
|
|
594
|
-
```jsonc
|
|
595
|
-
{
|
|
596
|
-
"event": "config.resolved",
|
|
597
|
-
"globalConfigPath": "/…/.pi/agent/extensions/pi-permission-system/config.json",
|
|
598
|
-
"globalConfigExists": true,
|
|
599
|
-
"projectConfigPath": "/…/my-project/.pi/extensions/pi-permission-system/config.json",
|
|
600
|
-
"projectConfigExists": false,
|
|
601
|
-
"agentsDir": "/…/.pi/agent/agents",
|
|
602
|
-
"agentsDirExists": true,
|
|
603
|
-
"projectAgentsDir": "/…/my-project/.pi/agent/agents",
|
|
604
|
-
"projectAgentsDirExists": false,
|
|
605
|
-
"legacyGlobalPolicyDetected": false,
|
|
606
|
-
"legacyProjectPolicyDetected": false,
|
|
607
|
-
"legacyExtensionConfigDetected": false
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### Architecture
|
|
612
|
-
|
|
613
|
-
```text
|
|
614
|
-
index.ts → Root Pi entrypoint shim
|
|
615
|
-
src/
|
|
616
|
-
├── index.ts → Extension bootstrap, permission checks, readable prompts, review logging, reload handling, and subagent forwarding
|
|
617
|
-
├── pattern-suggest.ts → Per-surface session approval pattern suggestions
|
|
618
|
-
├── bash-arity.ts → Curated arity dictionary for smarter bash session-approval patterns
|
|
619
|
-
├── session-rules.ts → Ephemeral session-scoped approval rules (Ruleset-based, wildcard patterns across all surfaces)
|
|
620
|
-
├── config-loader.ts → Unified config loader, merger, and legacy-path detection
|
|
621
|
-
├── config-paths.ts → Path derivation for global, project, and legacy config locations
|
|
622
|
-
├── config-reporter.ts → Resolved config path reporting for diagnostic logs
|
|
623
|
-
├── extension-config.ts → Runtime config normalization and defaults
|
|
624
|
-
├── logging.ts → File-only debug/review logging helpers
|
|
625
|
-
├── permission-manager.ts → Global/project policy loading, merging, and resolution with caching
|
|
626
|
-
├── skill-prompt-sanitizer.ts → Skill prompt parsing, multi-block sanitization, and skill-read path matching
|
|
627
|
-
├── bash-filter.ts → Bash command wildcard pattern matching
|
|
628
|
-
├── wildcard-matcher.ts → Shared wildcard pattern compilation and matching
|
|
629
|
-
├── common.ts → Shared utilities (YAML parsing, type guards, etc.)
|
|
630
|
-
├── tool-registry.ts → Registered tool name resolution
|
|
631
|
-
└── types.ts → TypeScript type definitions
|
|
632
|
-
tests/
|
|
633
|
-
├── permission-system.test.ts → Core permission, layering, forwarding, and policy tests
|
|
634
|
-
├── config-modal.test.ts → Config command and modal behavior tests
|
|
635
|
-
└── test-harness.ts → Shared lightweight test helpers
|
|
636
|
-
schemas/
|
|
637
|
-
└── permissions.schema.json → JSON Schema for policy validation
|
|
638
|
-
config/
|
|
639
|
-
└── config.example.json → Starter global policy template
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
#### Module Organization
|
|
643
|
-
|
|
644
|
-
The extension uses a modular architecture with shared utilities:
|
|
645
|
-
|
|
646
|
-
| Module | Purpose |
|
|
647
|
-
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
648
|
-
| `common.ts` | Shared utilities: `toRecord()`, `getNonEmptyString()`, `isPermissionState()`, `parseSimpleYamlMap()`, `extractFrontmatter()` |
|
|
649
|
-
| `wildcard-matcher.ts` | Compile-once wildcard patterns with last-match-wins evaluation: `compileWildcardPatterns()`, `findCompiledWildcardMatch()` |
|
|
650
|
-
| `permission-manager.ts` | Policy resolution with file stamp caching for performance |
|
|
651
|
-
| `bash-filter.ts` | Uses shared wildcard matcher for bash command patterns |
|
|
652
|
-
| `skill-prompt-sanitizer.ts` | Parses all available skill prompt blocks, removes denied skills, and tracks visible skill paths for read protection |
|
|
653
|
-
|
|
654
|
-
#### Performance Optimizations
|
|
655
|
-
|
|
656
|
-
- **File stamp caching**: Configurations are cached with file modification timestamps to avoid redundant reads
|
|
657
|
-
- **Pre-compiled patterns**: Wildcard patterns are compiled to regex once and reused across permission checks
|
|
658
|
-
- **Resolved permissions caching**: Merged agent+global permissions are cached per-agent with invalidation on file changes
|
|
659
|
-
|
|
660
|
-
### Threat Model
|
|
661
|
-
|
|
662
|
-
**Goal:** Enforce policy at the host level, not the model level.
|
|
663
|
-
|
|
664
|
-
**What this stops:**
|
|
665
|
-
|
|
666
|
-
- Agent calling tools it shouldn't use (e.g., `write`, dangerous `bash`)
|
|
667
|
-
- Tool switching attempts (calling non-existent tool names)
|
|
668
|
-
- Accidental escalation via skill loading
|
|
669
|
-
- Unapproved path-bearing tool access outside the active working directory when `external_directory` is `ask` or `deny`
|
|
670
|
-
|
|
671
|
-
**Limitations:**
|
|
672
|
-
|
|
673
|
-
- If a dangerous action is possible via an allowed tool, policy must explicitly restrict it
|
|
674
|
-
- This is a permission decision layer, not a sandbox
|
|
675
|
-
|
|
676
|
-
### Schema Validation
|
|
677
|
-
|
|
678
|
-
Validate your config against the included schema:
|
|
679
|
-
|
|
680
|
-
```bash
|
|
681
|
-
npx --yes ajv-cli@5 validate \
|
|
682
|
-
-s ./schemas/permissions.schema.json \
|
|
683
|
-
-d ./pi-permissions.valid.json
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
**Editor tip:** Add `"$schema": "./schemas/permissions.schema.json"` to your config for autocomplete support.
|
|
687
|
-
|
|
688
|
-
---
|
|
689
|
-
|
|
690
|
-
## Migration from pre-v2 layout
|
|
691
|
-
|
|
692
|
-
Before v2, config was split across two files:
|
|
693
|
-
|
|
694
|
-
- Policy: `~/.pi/agent/pi-permissions.jsonc`
|
|
695
|
-
- Runtime knobs: `<extension-install-dir>/config.json`
|
|
696
|
-
|
|
697
|
-
These are now consolidated into one file.
|
|
698
|
-
The extension detects legacy files and merges them with a warning for one release.
|
|
699
|
-
To migrate manually:
|
|
700
|
-
|
|
701
|
-
```bash
|
|
702
|
-
# Move the global policy file
|
|
703
|
-
mkdir -p ~/.pi/agent/extensions/pi-permission-system
|
|
704
|
-
mv ~/.pi/agent/pi-permissions.jsonc ~/.pi/agent/extensions/pi-permission-system/config.json
|
|
705
|
-
|
|
706
|
-
# If you had project-level policy:
|
|
707
|
-
mkdir -p .pi/extensions/pi-permission-system
|
|
708
|
-
mv .pi/agent/pi-permissions.jsonc .pi/extensions/pi-permission-system/config.json
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
Then add any runtime knobs (`debugLog`, `permissionReviewLog`, `yoloMode`) to the same file.
|
|
712
|
-
The old extension-root `config.json` is no longer read from the install directory.
|
|
713
|
-
|
|
714
|
-
> **Note:** Logs also moved from `<extension-install-dir>/logs/` to `~/.pi/agent/extensions/pi-permission-system/logs/`.
|
|
715
|
-
> Old log files are not deleted or migrated — they remain readable but no new entries are appended.
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Troubleshooting
|
|
720
|
-
|
|
721
|
-
| Problem | Cause | Solution |
|
|
722
|
-
| ------------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
723
|
-
| Config not applied (everything asks) | File not found or parse error | Verify the global config at `~/.pi/agent/extensions/pi-permission-system/config.json` (respects `PI_CODING_AGENT_DIR`); check for trailing commas |
|
|
724
|
-
| Per-agent override not applied | Frontmatter parsing issue | Ensure `---` delimiters at file top; keep YAML simple; restart session |
|
|
725
|
-
| Tool blocked as unregistered | Unknown tool name | Use a registered `mcp` tool for server tools: `{ "tool": "server:tool" }` |
|
|
726
|
-
| `/skill:<name>` blocked | Deny policy or confirmation unavailable | Check merged `skills` policy (global/project/agent layers). Active agent context is optional in the main session; `ask` still requires UI or forwarded confirmation. |
|
|
727
|
-
| External file path blocked | `special.external_directory` is `ask` without UI or `deny` | Allow/ask the special permission or keep file tools inside the active working directory. |
|
|
728
|
-
| Permission prompt is too verbose | Generic extension tool input is large | Built-in file tools are summarized automatically; third-party tools are capped to a bounded one-line JSON preview. |
|
|
729
|
-
|
|
730
|
-
---
|
|
70
|
+
|Document|Contents|
|
|
71
|
+
|---|---|
|
|
72
|
+
|[docs/configuration.md](docs/configuration.md)|Full policy reference, runtime knobs, per-agent overrides, recipes|
|
|
73
|
+
|[docs/session-approvals.md](docs/session-approvals.md)|Session-scoped rules, pattern suggestions, bash arity table|
|
|
74
|
+
|[docs/event-api.md](docs/event-api.md)|Event bus integration, decision broadcasts, RPC check/prompt|
|
|
75
|
+
|[docs/subagent-integration.md](docs/subagent-integration.md)|Permission forwarding, coexistence with subagent extensions|
|
|
76
|
+
|[docs/guides/permission-frontmatter-for-subagent-extensions.md](docs/guides/permission-frontmatter-for-subagent-extensions.md)|Convention guide for subagent extension authors|
|
|
77
|
+
|[docs/troubleshooting.md](docs/troubleshooting.md)|Common issues, diagnostic logging, threat model|
|
|
78
|
+
|[docs/migration/legacy-to-flat.md](docs/migration/legacy-to-flat.md)|Migration from pre-v2 config layout|
|
|
731
79
|
|
|
732
80
|
## Development
|
|
733
81
|
|
|
@@ -745,17 +93,7 @@ pnpm run check # build + lint:all + test
|
|
|
745
93
|
### Pre-commit hooks
|
|
746
94
|
|
|
747
95
|
This project uses [prek](https://prek.j178.dev/) to run Biome and markdownlint on staged files before each commit.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
1. Install prek ([installation guide](https://prek.j178.dev/installation/)).
|
|
751
|
-
2. Run `pnpm install` — the `prepare` script calls `prek install` automatically.
|
|
752
|
-
If prek is not installed, the script prints a warning and continues.
|
|
753
|
-
3. Hooks run automatically on `git commit`.
|
|
754
|
-
To skip in emergencies: `git commit --no-verify`.
|
|
755
|
-
|
|
756
|
-
The hook configuration lives in `prek.toml` at the repo root.
|
|
757
|
-
|
|
758
|
-
---
|
|
96
|
+
Run `pnpm install` to set up hooks automatically.
|
|
759
97
|
|
|
760
98
|
## Acknowledgments
|
|
761
99
|
|