@gotgenes/pi-permission-system 5.3.0 → 5.3.2
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 +20 -0
- package/README.md +50 -876
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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
|
+
## [5.3.2](https://github.com/gotgenes/pi-permission-system/compare/v5.3.1...v5.3.2) (2026-05-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* fix ordered list continuation, use realistic quick-start config ([dd26166](https://github.com/gotgenes/pi-permission-system/commit/dd261666e497a280902790a5a50f7daf70a511a4))
|
|
14
|
+
* **retro:** add retro notes for issue [#98](https://github.com/gotgenes/pi-permission-system/issues/98) ([bf2bbc6](https://github.com/gotgenes/pi-permission-system/commit/bf2bbc61c748222ff4bef8234ed8543d7a93ad27))
|
|
15
|
+
|
|
16
|
+
## [5.3.1](https://github.com/gotgenes/pi-permission-system/compare/v5.3.0...v5.3.1) (2026-05-05)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
* add permission frontmatter convention guide for subagent extensions ([c992959](https://github.com/gotgenes/pi-permission-system/commit/c99295960ad31e359fc1fbdea0ad45057d8366d8))
|
|
22
|
+
* add upstream issue template for subagent extension outreach ([79eef27](https://github.com/gotgenes/pi-permission-system/commit/79eef27fbee6786b5ef8c830ed0fce575d067b92))
|
|
23
|
+
* link permission frontmatter guide from README and target architecture ([df261fc](https://github.com/gotgenes/pi-permission-system/commit/df261fc36f9d9c527524b3301959a1be69bf8a51))
|
|
24
|
+
* plan shared permission frontmatter convention for subagent extensions ([#98](https://github.com/gotgenes/pi-permission-system/issues/98)) ([eec5763](https://github.com/gotgenes/pi-permission-system/commit/eec57637810a04c923ddff6e5991a8fbd95f4030))
|
|
25
|
+
* restructure README with inverted pyramid, extract reference docs ([db51142](https://github.com/gotgenes/pi-permission-system/commit/db51142e3bfc2283298c23a7a8517c72179f4611))
|
|
26
|
+
* **retro:** add retro notes for issue [#29](https://github.com/gotgenes/pi-permission-system/issues/29) ([be0620b](https://github.com/gotgenes/pi-permission-system/commit/be0620bc417297d43d5e7c0d04efd14f2844f147))
|
|
27
|
+
|
|
8
28
|
## [5.3.0](https://github.com/gotgenes/pi-permission-system/compare/v5.2.1...v5.3.0) (2026-05-05)
|
|
9
29
|
|
|
10
30
|
|
package/README.md
CHANGED
|
@@ -2,898 +2,82 @@
|
|
|
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
|
-
|
|
27
|
+
1. Create the global config file at `~/.pi/agent/extensions/pi-permission-system/config.json`:
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
1. Start Pi — the extension automatically loads and enforces your policy.
|
|
29
|
+
```jsonc
|
|
30
|
+
{
|
|
31
|
+
"permission": {
|
|
32
|
+
"*": "allow",
|
|
33
|
+
"bash": {
|
|
34
|
+
"rm -rf *": "deny",
|
|
35
|
+
"sudo *": "ask"
|
|
36
|
+
},
|
|
37
|
+
"external_directory": "ask"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
2. Start Pi — the extension automatically loads and enforces your policy.
|
|
66
43
|
|
|
67
44
|
All permissions use one of three states:
|
|
68
45
|
|
|
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 |
|
|
84
|
-
|
|
85
|
-
**Additional behaviors:**
|
|
46
|
+
|State|Behavior|
|
|
47
|
+
|---|---|
|
|
48
|
+
|`allow`|Permits the action silently|
|
|
49
|
+
|`deny`|Blocks the action with an error message|
|
|
50
|
+
|`ask`|Prompts the user for confirmation via UI|
|
|
86
51
|
|
|
87
|
-
|
|
88
|
-
-
|
|
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
|
|
52
|
+
When the dialog prompts, you can approve once or approve a pattern for the rest of the session.
|
|
53
|
+
See [docs/session-approvals.md](docs/session-approvals.md) for details on session-scoped rules and pattern suggestions.
|
|
95
54
|
|
|
96
55
|
## Configuration
|
|
97
56
|
|
|
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.
|
|
233
|
-
|
|
234
|
-
| Surface example | Description |
|
|
235
|
-
| --------------- | ----------- |
|
|
236
|
-
| `read`, `write`, `edit`, `grep`, `find`, `ls` | Canonical Pi built-in file tools |
|
|
237
|
-
| `bash` | Shell command execution |
|
|
238
|
-
| `mcp` | Registered MCP proxy tool |
|
|
239
|
-
| `task` | Delegation tool |
|
|
240
|
-
| `third_party_tool` | Any other registered extension tool |
|
|
241
|
-
|
|
242
|
-
```jsonc
|
|
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
|
-
```
|
|
279
|
-
|
|
280
|
-
### `mcp` surface
|
|
281
|
-
|
|
282
|
-
MCP permissions match against derived targets from tool input:
|
|
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
|
-
```
|
|
304
|
-
|
|
305
|
-
> **Note:** Baseline discovery targets auto-allow when any explicit `mcp: allow` rule exists.
|
|
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
|
-
```
|
|
367
|
-
|
|
368
|
-
`external_directory` is evaluated before the normal tool permission check. For example, `read: "allow"` can permit ordinary reads while `external_directory: "ask"` still requires confirmation before reading `../outside.txt` or an absolute path outside `ctx.cwd`.
|
|
369
|
-
Optional-path search tools (`find`, `grep`, `ls`) skip this check when no `path` is provided.
|
|
370
|
-
|
|
371
|
-
Bash commands are also covered: the extension extracts path-like tokens from the command string and applies the same gate when any resolve outside `ctx.cwd`.
|
|
372
|
-
Quoted strings are stripped first to reduce false positives.
|
|
373
|
-
This is a best-effort heuristic — variable expansion, subshells, and escaped quotes are not parsed.
|
|
374
|
-
OS device paths (`/dev/null`, `/dev/stdin`, `/dev/stdout`, `/dev/stderr`) are always excluded.
|
|
375
|
-
|
|
376
|
-
**Pi infrastructure read auto-allow** — Read-only tools (`read`, `find`, `grep`, `ls`) targeting Pi infrastructure directories are automatically allowed without triggering the gate, even when `external_directory` is `ask` or `deny`.
|
|
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
|
|
57
|
+
Config lives in one JSON file per scope:
|
|
458
58
|
|
|
459
|
-
|
|
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
|
-
## Event API
|
|
691
|
-
|
|
692
|
-
The extension emits events on Pi's `pi.events` bus so other extensions can observe permission decisions and integrate with the policy system without importing this package.
|
|
693
|
-
|
|
694
|
-
### Stability guarantee
|
|
695
|
-
|
|
696
|
-
Fields may be added to any payload, but existing fields will not be removed or renamed without a semver-major version bump.
|
|
697
|
-
The protocol version constant is exported from `src/permission-events.ts` and embedded in every RPC reply.
|
|
698
|
-
|
|
699
|
-
### Channel reference
|
|
700
|
-
|
|
701
|
-
|Channel|Direction|When|Payload type|
|
|
702
|
-
|---|---|---|---|
|
|
703
|
-
|`permissions:ready`|Broadcast|Once, immediately after load|`PermissionsReadyEvent`|
|
|
704
|
-
|`permissions:decision`|Broadcast|After every gate resolution|`PermissionDecisionEvent`|
|
|
705
|
-
|`permissions:rpc:check`|Request|On-demand|`PermissionsCheckRequest`|
|
|
706
|
-
|`permissions:rpc:check:reply:<requestId>`|Reply|After each check request|`PermissionsRpcReply<PermissionsCheckReplyData>`|
|
|
707
|
-
|`permissions:rpc:prompt`|Request|On-demand|`PermissionsPromptRequest`|
|
|
708
|
-
|`permissions:rpc:prompt:reply:<requestId>`|Reply|After prompt is resolved|`PermissionsRpcReply<PermissionsPromptReplyData>`|
|
|
709
|
-
|
|
710
|
-
### Surface 1 — Decision broadcasts
|
|
711
|
-
|
|
712
|
-
Every permission gate resolution emits a `permissions:decision` event, regardless of outcome.
|
|
713
|
-
This is useful for dashboards, telemetry, or audit overlays.
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
pi.events.on("permissions:decision", (raw) => {
|
|
717
|
-
const event = raw as import("@gotgenes/pi-permission-system").PermissionDecisionEvent;
|
|
718
|
-
console.log(event.surface, event.result, event.resolution);
|
|
719
|
-
// e.g. "bash" "allow" "user_approved_for_session"
|
|
720
|
-
});
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
Payload fields:
|
|
724
|
-
|
|
725
|
-
|Field|Type|Description|
|
|
726
|
-
|---|---|---|
|
|
727
|
-
|`surface`|`string`|Permission surface (`"bash"`, `"read"`, `"mcp"`, `"skill"`, `"external_directory"`, etc.)|
|
|
728
|
-
|`value`|`string`|Value evaluated (command, tool name, skill name, path)|
|
|
729
|
-
|`result`|`"allow" \| "deny"`|Final outcome|
|
|
730
|
-
|`resolution`|`string`|How the outcome was reached (see table below)|
|
|
731
|
-
|`origin`|`string \| null`|Config scope that contributed the winning rule|
|
|
732
|
-
|`agentName`|`string \| null`|Active agent name when known|
|
|
733
|
-
|`matchedPattern`|`string \| null`|Pattern from the winning rule|
|
|
734
|
-
|
|
735
|
-
Resolution values:
|
|
736
|
-
|
|
737
|
-
|Value|Meaning|
|
|
59
|
+
|Scope|Path|
|
|
738
60
|
|---|---|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|`session_approved`|Covered by a session-level approval from earlier in the same session|
|
|
742
|
-
|`infrastructure_auto_allowed`|Read of a Pi infrastructure path — auto-allowed|
|
|
743
|
-
|`user_approved`|User approved once via dialog|
|
|
744
|
-
|`user_approved_for_session`|User approved for the rest of the session|
|
|
745
|
-
|`user_denied`|User denied via dialog|
|
|
746
|
-
|`auto_approved`|Yolo mode — approved automatically without dialog|
|
|
747
|
-
|`confirmation_unavailable`|State was `ask` but no UI was available — blocked|
|
|
748
|
-
|
|
749
|
-
### Surface 2 — Policy query RPC
|
|
750
|
-
|
|
751
|
-
Other extensions can evaluate the current permission policy without importing this package.
|
|
752
|
-
The call is synchronous-style: emit a request, listen on a scoped reply channel.
|
|
753
|
-
|
|
754
|
-
```typescript
|
|
755
|
-
const requestId = crypto.randomUUID();
|
|
756
|
-
|
|
757
|
-
// Listen for the reply first
|
|
758
|
-
const unsub = pi.events.on(
|
|
759
|
-
`permissions:rpc:check:reply:${requestId}`,
|
|
760
|
-
(raw) => {
|
|
761
|
-
unsub();
|
|
762
|
-
const reply = raw as import("@gotgenes/pi-permission-system").PermissionsRpcReply<
|
|
763
|
-
import("@gotgenes/pi-permission-system").PermissionsCheckReplyData
|
|
764
|
-
>;
|
|
765
|
-
if (reply.success) {
|
|
766
|
-
console.log(reply.data?.result); // "allow" | "deny" | "ask"
|
|
767
|
-
}
|
|
768
|
-
},
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
// Then emit the request
|
|
772
|
-
pi.events.emit("permissions:rpc:check", {
|
|
773
|
-
requestId,
|
|
774
|
-
surface: "bash",
|
|
775
|
-
value: "git push",
|
|
776
|
-
agentName: "Worker", // optional
|
|
777
|
-
});
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
If the extension is not loaded, no reply arrives.
|
|
781
|
-
Callers should implement a timeout and treat no-reply as `deny` (graceful degradation).
|
|
782
|
-
|
|
783
|
-
Request fields:
|
|
784
|
-
|
|
785
|
-
|Field|Required|Description|
|
|
786
|
-
|---|---|---|
|
|
787
|
-
|`requestId`|Yes|Unique string; scopes the reply channel|
|
|
788
|
-
|`surface`|Yes|Permission surface to evaluate|
|
|
789
|
-
|`value`|No|Value to evaluate (command, name, path); defaults to `"*"`|
|
|
790
|
-
|`agentName`|No|Agent name for per-agent policy resolution|
|
|
791
|
-
|
|
792
|
-
Reply data fields (`PermissionsCheckReplyData`):
|
|
61
|
+
|Global|`~/.pi/agent/extensions/pi-permission-system/config.json`|
|
|
62
|
+
|Project|`<cwd>/.pi/extensions/pi-permission-system/config.json`|
|
|
793
63
|
|
|
794
|
-
|
|
795
|
-
|---|---|---|
|
|
796
|
-
|`result`|`"allow" \| "deny" \| "ask"`|Policy decision (including active session rules)|
|
|
797
|
-
|`matchedPattern`|`string \| null`|Matched rule pattern|
|
|
798
|
-
|`origin`|`string \| null`|Config scope of the winning rule|
|
|
64
|
+
Project overrides global; per-agent YAML frontmatter overrides both.
|
|
799
65
|
|
|
800
|
-
|
|
66
|
+
Within a surface map like `bash` or `mcp`, **last matching rule wins** — put broad catch-alls first and specific overrides after.
|
|
801
67
|
|
|
802
|
-
|
|
803
|
-
They can instead forward permission prompts to the parent session's UI via this RPC.
|
|
68
|
+
For the full reference — all surfaces, runtime knobs, per-agent overrides, merge semantics, and common recipes — see [docs/configuration.md](docs/configuration.md).
|
|
804
69
|
|
|
805
|
-
|
|
806
|
-
const requestId = crypto.randomUUID();
|
|
70
|
+
## Documentation
|
|
807
71
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
} else {
|
|
818
|
-
// deny — either user denied or no UI was available (error: "no_ui")
|
|
819
|
-
}
|
|
820
|
-
},
|
|
821
|
-
);
|
|
822
|
-
|
|
823
|
-
pi.events.emit("permissions:rpc:prompt", {
|
|
824
|
-
requestId,
|
|
825
|
-
surface: "bash",
|
|
826
|
-
value: "rm -rf /tmp/build",
|
|
827
|
-
message: "Allow rm -rf /tmp/build?",
|
|
828
|
-
agentName: "Explore", // optional
|
|
829
|
-
sessionLabel: "Allow rm *", // optional — label for the \"for this session\" option
|
|
830
|
-
});
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
The handler replies with `{ success: false, error: "no_ui" }` when no interactive session is available.
|
|
834
|
-
A successful reply contains:
|
|
835
|
-
|
|
836
|
-
|Field|Type|Description|
|
|
837
|
-
|---|---|---|
|
|
838
|
-
|`approved`|`boolean`|Whether the user approved|
|
|
839
|
-
|`state`|`string`|`"approved"`, `"approved_for_session"`, `"denied"`, or `"denied_with_reason"`|
|
|
840
|
-
|`denialReason`|`string` (optional)|User-provided denial reason|
|
|
841
|
-
|
|
842
|
-
### Ready event
|
|
843
|
-
|
|
844
|
-
The extension emits `permissions:ready` once immediately after it loads.
|
|
845
|
-
Consumers that start after the extension can check via a ping-style RPC check — the `permissions:rpc:check` handler is active as long as the extension is loaded.
|
|
846
|
-
|
|
847
|
-
```typescript
|
|
848
|
-
pi.events.on("permissions:ready", (raw) => {
|
|
849
|
-
const event = raw as import("@gotgenes/pi-permission-system").PermissionsReadyEvent;
|
|
850
|
-
console.log("Permission system loaded, protocol version:", event.protocolVersion);
|
|
851
|
-
});
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
---
|
|
855
|
-
|
|
856
|
-
## Migration from pre-v2 layout
|
|
857
|
-
|
|
858
|
-
Before v2, config was split across two files:
|
|
859
|
-
|
|
860
|
-
- Policy: `~/.pi/agent/pi-permissions.jsonc`
|
|
861
|
-
- Runtime knobs: `<extension-install-dir>/config.json`
|
|
862
|
-
|
|
863
|
-
These are now consolidated into one file.
|
|
864
|
-
The extension detects legacy files and merges them with a warning for one release.
|
|
865
|
-
To migrate manually:
|
|
866
|
-
|
|
867
|
-
```bash
|
|
868
|
-
# Move the global policy file
|
|
869
|
-
mkdir -p ~/.pi/agent/extensions/pi-permission-system
|
|
870
|
-
mv ~/.pi/agent/pi-permissions.jsonc ~/.pi/agent/extensions/pi-permission-system/config.json
|
|
871
|
-
|
|
872
|
-
# If you had project-level policy:
|
|
873
|
-
mkdir -p .pi/extensions/pi-permission-system
|
|
874
|
-
mv .pi/agent/pi-permissions.jsonc .pi/extensions/pi-permission-system/config.json
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
Then add any runtime knobs (`debugLog`, `permissionReviewLog`, `yoloMode`) to the same file.
|
|
878
|
-
The old extension-root `config.json` is no longer read from the install directory.
|
|
879
|
-
|
|
880
|
-
> **Note:** Logs also moved from `<extension-install-dir>/logs/` to `~/.pi/agent/extensions/pi-permission-system/logs/`.
|
|
881
|
-
> Old log files are not deleted or migrated — they remain readable but no new entries are appended.
|
|
882
|
-
|
|
883
|
-
---
|
|
884
|
-
|
|
885
|
-
## Troubleshooting
|
|
886
|
-
|
|
887
|
-
| Problem | Cause | Solution |
|
|
888
|
-
| ------------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
889
|
-
| 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 |
|
|
890
|
-
| Per-agent override not applied | Frontmatter parsing issue | Ensure `---` delimiters at file top; keep YAML simple; restart session |
|
|
891
|
-
| Tool blocked as unregistered | Unknown tool name | Use a registered `mcp` tool for server tools: `{ "tool": "server:tool" }` |
|
|
892
|
-
| `/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. |
|
|
893
|
-
| 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. |
|
|
894
|
-
| 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. |
|
|
895
|
-
|
|
896
|
-
---
|
|
72
|
+
|Document|Contents|
|
|
73
|
+
|---|---|
|
|
74
|
+
|[docs/configuration.md](docs/configuration.md)|Full policy reference, runtime knobs, per-agent overrides, recipes|
|
|
75
|
+
|[docs/session-approvals.md](docs/session-approvals.md)|Session-scoped rules, pattern suggestions, bash arity table|
|
|
76
|
+
|[docs/event-api.md](docs/event-api.md)|Event bus integration, decision broadcasts, RPC check/prompt|
|
|
77
|
+
|[docs/subagent-integration.md](docs/subagent-integration.md)|Permission forwarding, coexistence with subagent extensions|
|
|
78
|
+
|[docs/guides/permission-frontmatter-for-subagent-extensions.md](docs/guides/permission-frontmatter-for-subagent-extensions.md)|Convention guide for subagent extension authors|
|
|
79
|
+
|[docs/troubleshooting.md](docs/troubleshooting.md)|Common issues, diagnostic logging, threat model|
|
|
80
|
+
|[docs/migration/legacy-to-flat.md](docs/migration/legacy-to-flat.md)|Migration from pre-v2 config layout|
|
|
897
81
|
|
|
898
82
|
## Development
|
|
899
83
|
|
|
@@ -911,17 +95,7 @@ pnpm run check # build + lint:all + test
|
|
|
911
95
|
### Pre-commit hooks
|
|
912
96
|
|
|
913
97
|
This project uses [prek](https://prek.j178.dev/) to run Biome and markdownlint on staged files before each commit.
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
1. Install prek ([installation guide](https://prek.j178.dev/installation/)).
|
|
917
|
-
2. Run `pnpm install` — the `prepare` script calls `prek install` automatically.
|
|
918
|
-
If prek is not installed, the script prints a warning and continues.
|
|
919
|
-
3. Hooks run automatically on `git commit`.
|
|
920
|
-
To skip in emergencies: `git commit --no-verify`.
|
|
921
|
-
|
|
922
|
-
The hook configuration lives in `prek.toml` at the repo root.
|
|
923
|
-
|
|
924
|
-
---
|
|
98
|
+
Run `pnpm install` to set up hooks automatically.
|
|
925
99
|
|
|
926
100
|
## Acknowledgments
|
|
927
101
|
|