pi-permission-system 0.1.0

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 ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-02
11
+
12
+ ### Changed
13
+ - Reorganized repository structure to match standard extension layout:
14
+ - moved implementation and tests into `src/`
15
+ - added root `index.ts` shim for Pi auto-discovery
16
+ - standardized TypeScript project settings with Bundler module resolution
17
+ - Added package distribution metadata and scripts, including `pi.extensions` and publish file whitelist.
18
+ - Added repository scaffolding files (`README.md`, `CHANGELOG.md`, `LICENSE`, `.gitignore`, `.npmignore`) and config starter template.
19
+
20
+ ### Preserved
21
+ - Global permission config path semantics remained `~/.pi/agent/pi-permissions.jsonc`.
22
+ - Permission schema location remained `schemas/permissions.schema.json`.
23
+ - Permission enforcement behavior remained intact.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MasuRii
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,421 @@
1
+ # pi-permission-system
2
+
3
+ Permission enforcement extension for the Pi coding agent.
4
+
5
+ This extension enforces **tool**, **bash**, **MCP**, **skill**, and a small set of **special** permission policies.
6
+ It is designed to reduce accidental or policy-violating actions by:
7
+
8
+ - hiding disallowed tools from the agent *before it starts* (reduces “try another tool” behavior), and
9
+ - blocking/asking/allowing at **tool call time** (the actual enforcement point).
10
+
11
+ > Global runtime policy file (JSON-with-comments):
12
+ >
13
+ > `~/.pi/agent/pi-permissions.jsonc`
14
+
15
+ ![alt text](asset/pi-permission-system.png)
16
+
17
+ ---
18
+
19
+ ## Threat model / goal
20
+
21
+ **Goal:** Provide a centralized, deterministic permission gate for the Pi agent runtime so that *policy is enforced by the host*, not by the model.
22
+
23
+ **Threat model (what this is meant to stop):**
24
+
25
+ - The agent calling tools it should not use (e.g., `write`, dangerous `bash`, broad MCP actions).
26
+ - “Tool switching” attempts (agent tries a different tool name or a server tool directly).
27
+ - Accidental escalation via skill loading or reading skill files from disk.
28
+
29
+ **Non-goals / limitations:**
30
+
31
+ - If a dangerous action is possible using an allowed tool (e.g., destructive shell commands via allowed `bash`), policy must explicitly restrict that.
32
+ - This is not a sandbox; it is a permission decision layer.
33
+
34
+ ---
35
+
36
+ ## How it integrates with Pi
37
+
38
+ This extension integrates via Pi’s extension lifecycle hooks:
39
+
40
+ - `before_agent_start`
41
+ - Filters the active tool list via `pi.setActiveTools(...)` based on the resolved permission policy.
42
+ - Filters the `<available_skills> ... </available_skills>` section in the system prompt so skills with `deny` are removed.
43
+ - `tool_call`
44
+ - Enforces permissions for every tool call.
45
+ - When a permission is `ask`, it prompts the user via the UI confirmation dialog.
46
+ - `input`
47
+ - Intercepts `/skill:<name>` requests and enforces the `skills` policy before the skill is loaded.
48
+
49
+ Additional enforcement behaviors:
50
+
51
+ - **Unknown/unregistered tools are blocked** before permission checks (prevents bypass via calling non-existent tool names).
52
+ - The delegation tool **`task` is restricted to the `orchestrator` agent**.
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ### Local extension folder
59
+
60
+ Place this folder in one of:
61
+
62
+ - Global: `~/.pi/agent/extensions/pi-permission-system`
63
+ - Project: `.pi/extensions/pi-permission-system`
64
+
65
+ Pi auto-discovers these paths.
66
+
67
+ ---
68
+
69
+ ## Configuration
70
+
71
+ ### Global policy file (required)
72
+
73
+ Runtime policy is loaded from:
74
+
75
+ - `~/.pi/agent/pi-permissions.jsonc`
76
+
77
+ Notes:
78
+
79
+ - The file is parsed as JSON after stripping `// ...` and `/* ... */` comments.
80
+ - Trailing commas are **not** supported (it still uses `JSON.parse` after comment stripping).
81
+ - If the file cannot be read or parsed, the extension falls back to an empty config with a default of **`ask`** for all categories.
82
+
83
+ ### Per-agent overrides (frontmatter)
84
+
85
+ Per-agent overrides are loaded from the agent markdown file:
86
+
87
+ - `~/.pi/agent/agents/<agent>.md`
88
+
89
+ Add a YAML frontmatter block at the top of the agent file and include a `permission:` map.
90
+ Example:
91
+
92
+ ```md
93
+ ---
94
+ name: my-agent
95
+ permission:
96
+ defaultPolicy:
97
+ tools: ask
98
+ bash: ask
99
+ mcp: ask
100
+ skills: ask
101
+ special: ask
102
+
103
+ # Recommended: configure permissions by section.
104
+ tools:
105
+ read: allow
106
+ write: deny
107
+ bash: ask
108
+
109
+ # Alternative (equivalent): direct tool keys also work, but avoid duplicates.
110
+ # read: allow
111
+ # write: deny
112
+
113
+ bash:
114
+ git status: allow
115
+ git *: ask
116
+
117
+ mcp:
118
+ mcp_status: allow
119
+ # Prefer the underscore form in frontmatter (no ':' parsing edge cases):
120
+ myServer_*: ask
121
+
122
+ skills:
123
+ "*": ask
124
+ ---
125
+
126
+ # Agent prompt
127
+ ...
128
+ ```
129
+
130
+ **Precedence:** agent frontmatter overrides global config (shallow-merged per section).
131
+
132
+ **Frontmatter parser limitations:** the agent override parser is intentionally minimal (a simple YAML-ish map parser). Stick to:
133
+
134
+ - `key: value` scalars and nested maps via indentation
135
+ - `#` comments
136
+
137
+ Avoid advanced YAML features (arrays, multi-line scalars, anchors, etc.).
138
+
139
+ **Reload behavior:**
140
+
141
+ - Global config (`pi-permissions.jsonc`) is re-read during permission checks, so edits typically apply immediately.
142
+ - Agent frontmatter overrides are cached for some checks (notably tool exposure/mapping). If an override change does not appear to apply, restart the session.
143
+
144
+ ### Config example
145
+
146
+ A starter template is provided at:
147
+
148
+ - `config/config.example.json`
149
+
150
+ ---
151
+
152
+ ## Policy reference
153
+
154
+ The policy file is a JSON object with these top-level keys:
155
+
156
+ - `defaultPolicy` (required)
157
+ - `tools` (built-in tools)
158
+ - `bash` (command patterns)
159
+ - `mcp` (MCP target patterns)
160
+ - `skills` (skill name patterns)
161
+ - `special` (reserved/special checks)
162
+
163
+ All permission states are one of:
164
+
165
+ - `allow`
166
+ - `deny`
167
+ - `ask` (requires UI confirmation)
168
+
169
+ ### `defaultPolicy`
170
+
171
+ Sets defaults when no specific rule matches.
172
+
173
+ ```jsonc
174
+ {
175
+ "defaultPolicy": {
176
+ "tools": "ask",
177
+ "bash": "ask",
178
+ "mcp": "ask",
179
+ "skills": "ask",
180
+ "special": "ask"
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### `tools`
186
+
187
+ Controls built-in tools by exact name (no wildcard matching):
188
+
189
+ - `bash`, `read`, `write`, `edit`, `grep`, `find`, `ls`
190
+
191
+ Example:
192
+
193
+ ```jsonc
194
+ {
195
+ "tools": {
196
+ "read": "allow",
197
+ "write": "deny",
198
+ "edit": "deny"
199
+ }
200
+ }
201
+ ```
202
+
203
+ **Bash nuance:** setting `tools.bash` affects the *default* decision for bash commands, but `bash` patterns (see below) can still provide command-level allow/deny/ask.
204
+
205
+ ### `bash`
206
+
207
+ Command permissions are matched against the full command string using `*` wildcards.
208
+ Patterns are anchored (`^...$`) and matched by specificity:
209
+
210
+ 1. fewer `*` wildcards wins
211
+ 2. then longer literal text wins
212
+ 3. then longer overall pattern wins
213
+
214
+ Example:
215
+
216
+ ```jsonc
217
+ {
218
+ "bash": {
219
+ "git status": "allow",
220
+ "git *": "ask",
221
+ "rm -rf *": "deny"
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### `skills`
227
+
228
+ Matches skill names using `*` wildcards (same specificity approach as above).
229
+
230
+ ```jsonc
231
+ {
232
+ "skills": {
233
+ "*": "ask",
234
+ "dangerous-*": "deny"
235
+ }
236
+ }
237
+ ```
238
+
239
+ ### `mcp`
240
+
241
+ MCP permissions match against a set of derived “targets” from the `mcp` tool input. You can write rules against:
242
+
243
+ - baseline operations: `mcp_status`, `mcp_list`, `mcp_search`, `mcp_describe`, `mcp_connect`
244
+ - the server name (e.g. `myServer`)
245
+ - server/tool combinations (e.g. `myServer:search`, `myServer_search`)
246
+ - generic categories like `mcp_call`
247
+
248
+ Example:
249
+
250
+ ```jsonc
251
+ {
252
+ "mcp": {
253
+ "mcp_status": "allow",
254
+ "mcp_list": "allow",
255
+ "mcp_search": "allow",
256
+
257
+ "myServer": "ask",
258
+ "myServer:*": "ask",
259
+
260
+ "dangerousServer": "deny"
261
+ }
262
+ }
263
+ ```
264
+
265
+ **Baseline auto-allow behavior:** when you allow *any* MCP rule (or set `defaultPolicy.mcp` to `allow`), baseline discovery targets like `mcp_status` may be auto-allowed to support normal MCP discovery flows.
266
+
267
+ ### `special`
268
+
269
+ The schema includes these keys:
270
+
271
+ - `doom_loop`
272
+ - `external_directory`
273
+ - `tool_call_limit`
274
+
275
+ Only `doom_loop` and `external_directory` are recognized as “special permission names” by this extension’s permission manager.
276
+ `tool_call_limit` is present in the schema for forward compatibility and is **not enforced by this extension version**.
277
+
278
+ ---
279
+
280
+ ## Schema reference & validation
281
+
282
+ - Schema file (in this repo): `schemas/permissions.schema.json`
283
+
284
+ How to validate:
285
+
286
+ 1. Ensure your config is valid JSON (remove comments if your validator does not support JSONC).
287
+ 2. Use any JSON Schema validator against `schemas/permissions.schema.json`.
288
+
289
+ Example (Ajv CLI):
290
+
291
+ ```bash
292
+ # Validate a comment-free copy of your config
293
+ npx --yes ajv-cli@5 validate \
294
+ -s ./schemas/permissions.schema.json \
295
+ -d ./pi-permissions.valid.json
296
+ ```
297
+
298
+ Editor tip: you may add a `$schema` field to your config so editors can provide autocomplete/validation.
299
+
300
+ ---
301
+
302
+ ## Common recipes
303
+
304
+ ### 1) Read-only by default (deny writes)
305
+
306
+ ```jsonc
307
+ {
308
+ "defaultPolicy": { "tools": "ask", "bash": "ask", "mcp": "ask", "skills": "ask", "special": "ask" },
309
+ "tools": {
310
+ "read": "allow",
311
+ "grep": "allow",
312
+ "find": "allow",
313
+ "ls": "allow",
314
+
315
+ "write": "deny",
316
+ "edit": "deny"
317
+ }
318
+ }
319
+ ```
320
+
321
+ ### 2) Allow only a small bash surface
322
+
323
+ ```jsonc
324
+ {
325
+ "defaultPolicy": { "tools": "ask", "bash": "deny", "mcp": "ask", "skills": "ask", "special": "ask" },
326
+ "bash": {
327
+ "git status": "allow",
328
+ "git diff": "allow",
329
+ "git *": "ask"
330
+ }
331
+ }
332
+ ```
333
+
334
+ ### 3) Allow MCP discovery, ask on server calls
335
+
336
+ ```jsonc
337
+ {
338
+ "defaultPolicy": { "tools": "ask", "bash": "ask", "mcp": "ask", "skills": "ask", "special": "ask" },
339
+ "mcp": {
340
+ "mcp_status": "allow",
341
+ "mcp_list": "allow",
342
+ "mcp_search": "allow",
343
+ "mcp_describe": "allow",
344
+
345
+ "*": "ask"
346
+ }
347
+ }
348
+ ```
349
+
350
+ ### 4) Tighten one agent only (frontmatter override)
351
+
352
+ In `~/.pi/agent/agents/reviewer.md`:
353
+
354
+ ```md
355
+ ---
356
+ permission:
357
+ tools:
358
+ write: deny
359
+ edit: deny
360
+ bash:
361
+ "*": deny
362
+ ---
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Troubleshooting
368
+
369
+ ### “My config isn’t applied (everything still asks)”
370
+
371
+ - Confirm the file is at `~/.pi/agent/pi-permissions.jsonc`.
372
+ - Check for JSON parse errors (common causes: trailing commas, missing quotes).
373
+ - If parsing fails, the extension silently falls back to an empty config (defaults to `ask`).
374
+
375
+ ### “My per-agent override isn’t applied”
376
+
377
+ - Confirm the file exists at `~/.pi/agent/agents/<agent>.md`.
378
+ - Ensure the frontmatter starts at the very top of the file and is delimited by `---`.
379
+ - Keep the `permission:` block simple (maps + scalars only).
380
+ - Restart the Pi session if you edited agent frontmatter during an active session (some override data is cached).
381
+
382
+ ### “Tool was blocked as unregistered”
383
+
384
+ - The extension blocks unregistered tool names before permission checks.
385
+ - If you intended to call an MCP server tool directly, use the built-in `mcp` tool instead (e.g. `{ "tool": "server:tool" }`).
386
+
387
+ ### “/skill:<name> is blocked”
388
+
389
+ - Skill loading requires a known active agent context and a non-`deny` `skills` policy.
390
+ - If you run headless (no UI), `ask` decisions may effectively behave like blocks because they cannot be confirmed.
391
+
392
+ ---
393
+
394
+ ## Development
395
+
396
+ ```bash
397
+ npm run build
398
+ npm run lint
399
+ npm run test
400
+ npm run check
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Project layout
406
+
407
+ - `index.ts` - root Pi entrypoint shim
408
+ - `src/index.ts` - extension bootstrap + enforcement integration (`before_agent_start`, `tool_call`, `input`)
409
+ - `src/permission-manager.ts` - policy loading, merging, and permission resolution
410
+ - `src/bash-filter.ts` - bash wildcard matcher / specificity sorting
411
+ - `src/tool-registry.ts` - registered tool name resolution + pre-check
412
+ - `src/types.ts` - shared permission types
413
+ - `src/test.ts` - TypeScript test runner
414
+ - `schemas/permissions.schema.json` - permission config schema
415
+ - `config/config.example.json` - starter config template
416
+
417
+ ---
418
+
419
+ ## License
420
+
421
+ MIT
@@ -0,0 +1,27 @@
1
+ {
2
+ "defaultPolicy": {
3
+ "tools": "ask",
4
+ "bash": "ask",
5
+ "mcp": "ask",
6
+ "skills": "ask",
7
+ "special": "ask"
8
+ },
9
+ "tools": {
10
+ "read": "allow",
11
+ "write": "deny"
12
+ },
13
+ "bash": {
14
+ "git status": "allow",
15
+ "git *": "ask"
16
+ },
17
+ "mcp": {
18
+ "mcp_status": "allow"
19
+ },
20
+ "skills": {
21
+ "*": "ask"
22
+ },
23
+ "special": {
24
+ "doom_loop": "deny",
25
+ "external_directory": "ask"
26
+ }
27
+ }
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import permissionSystemExtension from "./src/index.js";
2
+
3
+ export default permissionSystemExtension;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "pi-permission-system",
3
+ "version": "0.1.0",
4
+ "description": "Permission enforcement extension for the Pi coding agent.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "config/config.example.json",
14
+ "schemas/permissions.schema.json",
15
+ "README.md",
16
+ "CHANGELOG.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
21
+ "lint": "npm run build",
22
+ "test": "bun ./src/test.ts",
23
+ "check": "npm run lint && npm run test"
24
+ },
25
+ "keywords": [
26
+ "pi-package",
27
+ "pi",
28
+ "pi-extension",
29
+ "permissions",
30
+ "policy",
31
+ "coding-agent"
32
+ ],
33
+ "author": "MasuRii",
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "pi": {
42
+ "extensions": [
43
+ "./index.ts"
44
+ ]
45
+ },
46
+ "peerDependencies": {
47
+ "@mariozechner/pi-coding-agent": "*",
48
+ "@sinclair/typebox": "*"
49
+ }
50
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://pi-coding-agent.local/schemas/permissions.schema.json",
4
+ "title": "PI Permission Configuration",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string"
10
+ },
11
+ "defaultPolicy": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["tools", "bash", "mcp", "skills"],
15
+ "properties": {
16
+ "tools": {
17
+ "$ref": "#/$defs/permissionState"
18
+ },
19
+ "bash": {
20
+ "$ref": "#/$defs/permissionState"
21
+ },
22
+ "mcp": {
23
+ "$ref": "#/$defs/permissionState"
24
+ },
25
+ "skills": {
26
+ "$ref": "#/$defs/permissionState"
27
+ },
28
+ "special": {
29
+ "$ref": "#/$defs/permissionState"
30
+ }
31
+ }
32
+ },
33
+ "tools": {
34
+ "$ref": "#/$defs/permissionMap"
35
+ },
36
+ "bash": {
37
+ "$ref": "#/$defs/permissionMap"
38
+ },
39
+ "mcp": {
40
+ "$ref": "#/$defs/permissionMap"
41
+ },
42
+ "skills": {
43
+ "$ref": "#/$defs/permissionMap"
44
+ },
45
+ "special": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "properties": {
49
+ "doom_loop": {
50
+ "$ref": "#/$defs/permissionState"
51
+ },
52
+ "external_directory": {
53
+ "$ref": "#/$defs/permissionState"
54
+ },
55
+ "tool_call_limit": {
56
+ "oneOf": [
57
+ {
58
+ "$ref": "#/$defs/permissionState"
59
+ },
60
+ {
61
+ "type": "integer",
62
+ "minimum": 0
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ }
68
+ },
69
+ "required": ["defaultPolicy"],
70
+ "$defs": {
71
+ "permissionState": {
72
+ "type": "string",
73
+ "enum": ["allow", "deny", "ask"]
74
+ },
75
+ "permissionMap": {
76
+ "type": "object",
77
+ "propertyNames": {
78
+ "type": "string",
79
+ "minLength": 1
80
+ },
81
+ "additionalProperties": {
82
+ "$ref": "#/$defs/permissionState"
83
+ }
84
+ }
85
+ }
86
+ }