mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
184 lines
15 KiB
Markdown
184 lines
15 KiB
Markdown
<p align="center">
|
|
<img src="docs/assets/logo.png" alt="pi-permission-system logo">
|
|
</p>
|
|
|
|
# @gotgenes/pi-permission-system
|
|
|
|
[](https://www.npmjs.com/package/@gotgenes/pi-permission-system) [](https://github.com/gotgenes/pi-packages/actions/workflows/ci.yml) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](https://pnpm.io/) [](https://pi.mariozechner.at/)
|
|
|
|
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.
|
|
|
|
> **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`.
|
|
> It has diverged substantially from upstream in config format, internal architecture, and permission model.
|
|
>
|
|
> **my-pi maintenance note:** This directory was imported from upstream tag `pi-permission-system-v26.2.1` at commit `ec4fdb11343dc94f7185b113e559a4cf9f8dc035`. It is loaded from source and maintained directly by my-pi; npm `dist` output is not used. The local delegation envelope accepts an authorizer `allow` for built-in read-only path tools (`read`, `find`, `grep`, `ls`) accessing `external_directory`, while write, edit, bash, unknown tools, and all `path` asks still defer to human confirmation.
|
|
|
|
## What It Does
|
|
|
|
- **Hides disallowed tools** before the agent starts — no wasted turns probing for blocked tools
|
|
- **Enforces allow / ask / deny** at tool-call time with UI confirmation dialogs
|
|
- **Reviews unmatched `ask` requests automatically** with an integrated Codex Guardian-style reviewer; only `defer` reaches the human fallback
|
|
- **Applies a separate hard-guard policy first** so project rules, session grants, and model review cannot override high-risk denies
|
|
- **Controls bash commands** with wildcard pattern matching (`git *: ask`, `rm -rf *: deny`)
|
|
- **Gates MCP and skill access** at server, tool, and skill-name granularity
|
|
- **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once, matching both the path as referenced and its symlink-resolved form so a deny cannot be evaded through a symlink alias
|
|
- **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
|
|
- **Fails closed** — an internal gate error blocks the tool (with a `gate_error` review-log entry), and an unparseable bash command — or an indirection wrapper that hides the gated command (`bash -c`/`eval`, `sudo`, `env`, `xargs`, `find -exec`, …) — prompts (`ask`) rather than passing silently
|
|
- **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
|
|
- **Broadcasts UI prompt events** — `permissions:ui_prompt` fires only when the permission system is about to invoke the active user-facing permission UI
|
|
- **Native in-process subagent integration** — [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) and the source-maintained `my-pi` fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) register child sessions automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
|
|
|
|
## Install
|
|
|
|
```bash
|
|
pi install npm:@gotgenes/pi-permission-system
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
1. Create ordinary policy at `~/.pi/agent/extensions/pi-permission-system/policy.jsonc`:
|
|
|
|
```jsonc
|
|
{
|
|
"authorizerChain": ["auto-review"],
|
|
"permission": {
|
|
"*": "ask",
|
|
"read": "allow",
|
|
"bash": {
|
|
"*": "ask",
|
|
"git status*": "allow"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Create independent hard guards at `~/.pi/agent/extensions/pi-permission-system/hard-guards.jsonc`:
|
|
|
|
```jsonc
|
|
{
|
|
"version": 1,
|
|
"permission": {
|
|
"*": "allow",
|
|
"*.env": "deny",
|
|
"*.env.*": "deny",
|
|
"*.env.example": "allow",
|
|
"~/.ssh/**": "deny"
|
|
}
|
|
}
|
|
```
|
|
|
|
3. Restart Pi or run `/reload`. Ordinary `ask` requests now reach AutoReview first; only `defer` opens the human dialog.
|
|
|
|
The `path` surface is a cross-cutting gate that applies to **all** file access — Pi tools, bash commands, MCP calls, and extension tools alike.
|
|
Extension and MCP tools that operate on paths (via `input.path`, MCP's `input.arguments.path`, or a registered access extractor) are gated by default, so a `path` deny cannot be overridden by a per-tool allow — making it the right place to protect sensitive files like `.env` or `~/.ssh/*` from every tool at once.
|
|
A `path` pattern matches both the path as the agent references it and its canonical (symlink-resolved) form, so a deny still fires when a symlink aliases a sensitive target.
|
|
|
|
For per-tool path patterns (`read`, `write`, `edit`, `find`, `grep`, `ls`), patterns are matched against the file path from `input.path`.
|
|
This lets you express rules like "allow reads but deny `.env` files" at the individual tool level.
|
|
Like the cross-cutting `path` surface, per-tool patterns match both the referenced path and its canonical (symlink-resolved) form, so a per-tool deny resists symlink-alias evasion.
|
|
When Pi's current working directory is known, relative path inputs also match their cwd-normalized absolute form, so `src/App.jsx` can match both `src/*` and `/workspace/project/*`.
|
|
|
|
The `external_directory` surface is the CWD-boundary gate: it decides whether reaching **outside** the working tree is allowed, and accepts a pattern map so you can allow specific outside-CWD directories without opening up all external access.
|
|
This is the right surface for silencing repeated prompts on a local cache like `~/.cargo/registry` — allow it here, not on `path`:
|
|
|
|
```jsonc
|
|
{
|
|
"permission": {
|
|
"external_directory": {
|
|
"*": "ask",
|
|
"~/.cargo/registry/*": "allow"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The trailing `*` is greedy and crosses subdirectory boundaries, so it allows every file beneath the directory; a bare `~/.cargo/registry` matches only the directory entry itself.
|
|
|
|
Four layers compose with most-restrictive-wins: `path` (cross-cutting) → `external_directory` (CWD boundary) → per-tool patterns → `bash` command patterns.
|
|
Because `ask` is more restrictive than `allow`, a `path` allow cannot loosen an `external_directory: ask` boundary — allow outside-CWD directories on `external_directory`.
|
|
See [docs/configuration.md](docs/configuration.md) for the full recipe.
|
|
|
|
## Configuration
|
|
|
|
The integrated extension uses two independent JSONC files:
|
|
|
|
| Purpose | Scope | Path |
|
|
| ------- | ----- | ---- |
|
|
| Ordinary policy, runtime knobs, AutoReview | Global | `~/.pi/agent/extensions/pi-permission-system/policy.jsonc` |
|
|
| Ordinary policy override | Project | `<cwd>/.pi/extensions/pi-permission-system/policy.jsonc` |
|
|
| Non-overridable hard guards | Global only | `~/.pi/agent/extensions/pi-permission-system/hard-guards.jsonc` |
|
|
|
|
The bundle seeds missing files once with mode `0600` and never overwrites user edits. On first load, an existing legacy global `config.json` is copied to `policy.jsonc`; the legacy file is left untouched. Project policy is loaded only for a trusted project. Hard guards are never loaded from the project and are evaluated before ordinary policy and session grants.
|
|
|
|
Ordinary policy remains flat `allow` / `ask` / `deny`. The bundle default asks for unmatched requests and names the built-in `auto-review` authorizer. The evaluation order is:
|
|
|
|
1. global hard guards (`deny` is final; `allow` is only an exception inside that file);
|
|
2. ordinary global/project/per-agent policy and current session grants;
|
|
3. built-in AutoReview for an ordinary `ask`;
|
|
4. the existing human prompt only when AutoReview returns `defer`.
|
|
|
|
`autoReview` and `analysis` are regular top-level keys in `policy.jsonc`. AutoReview defaults to `openai-codex/codex-auto-review`, low reasoning, and a 90-second budget. Model/config/authentication/timeout errors fail safe to `defer`.
|
|
|
|
Completed model verdicts (`allow` and `deny`) are appended to an owner-only, sanitized count ledger. Rationales, transcripts, and raw tool payloads are not retained. After 500 new verdicts, `agent_settled` launches one isolated, tool-free background AgentSession using the configured review model. The validated proposal is persisted and delivered as a follow-up in the same conversation. It never edits either policy file automatically: the active agent must present the candidates and wait for the user's explicit selection and confirmation. A pending batch survives session shutdown and resumes in the next session.
|
|
|
|
For the full policy surface and merge semantics, see [docs/configuration.md](docs/configuration.md). The integrated reviewer provenance is recorded in [docs/auto-review/UPSTREAM.md](docs/auto-review/UPSTREAM.md).
|
|
|
|
## Upgrading
|
|
|
|
### 22.0.0 — project config requires project trust
|
|
|
|
Project-scoped configuration (the project `policy.jsonc` and project-agent frontmatter — both permission policy and runtime knobs such as `yoloMode`) is loaded only when Pi reports the project as trusted.
|
|
In an untrusted directory, only global config applies; a skip is surfaced with a warning and a `project_trust.skipped` review-log entry.
|
|
Grant project trust (or set `defaultProjectTrust`) to load a project's config.
|
|
See [docs/migration/0644-project-trust-gating.md](docs/migration/0644-project-trust-gating.md).
|
|
|
|
### 16.0.0 — the bash gate now fails closed
|
|
|
|
The permission gate fails closed: an internal gate error blocks the tool (with a `gate_error` review-log entry) instead of running it ungated, and a non-empty bash command that cannot be parsed resolves to `ask` (sentinel `<unparseable-bash-command>`) rather than falling through to a permissive top-level `*`.
|
|
Commands that previously slipped through silently on the error or empty-parse path now block or prompt.
|
|
|
|
If you relied on the old permissive behavior for bash, set an explicit permissive bash policy — `"bash": { "*": "allow" }` — which also suppresses the new startup warning emitted when a top-level `"*": "allow"` leaves bash ungated.
|
|
|
|
## Documentation
|
|
|
|
| Document | Contents |
|
|
| ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
| [docs/configuration.md](docs/configuration.md) | Full policy reference, runtime knobs, per-agent overrides, recipes |
|
|
| [docs/session-approvals.md](docs/session-approvals.md) | Session-scoped rules, pattern suggestions, bash arity table |
|
|
| [docs/cross-extension-api.md](docs/cross-extension-api.md) | Cross-extension service accessor, event bus integration, prompt and decision broadcasts |
|
|
| [docs/subagent-integration.md](docs/subagent-integration.md) | Permission forwarding, coexistence with subagent extensions |
|
|
| [docs/guides/permission-frontmatter-for-subagent-extensions.md](docs/guides/permission-frontmatter-for-subagent-extensions.md) | Convention guide for subagent extension authors |
|
|
| [docs/opencode-compatibility.md](docs/opencode-compatibility.md) | OpenCode compatibility — shared concepts, divergences, porting guide |
|
|
| [docs/troubleshooting.md](docs/troubleshooting.md) | Common issues, diagnostic logging, threat model |
|
|
| [docs/migration/legacy-to-flat.md](docs/migration/legacy-to-flat.md) | Migration from pre-v2 config layout |
|
|
| [docs/migration/strict-config-validation.md](docs/migration/strict-config-validation.md) | Strict config validation (breaking) — rejected configs, and the cross-scope fail-closed clamp |
|
|
| [docs/migration/0644-project-trust-gating.md](docs/migration/0644-project-trust-gating.md) | Project-trust gating (breaking) — project config loads only after project trust |
|
|
| [docs/migration/0745-prompt-payload-contracts.md](docs/migration/0745-prompt-payload-contracts.md) | Prompt payload contracts (breaking) — the forwarded wire, the `ui_prompt` broadcast, and the deprecated preview caps |
|
|
| [docs/migration/0746-review-log-fields.md](docs/migration/0746-review-log-fields.md) | Review-log fields (breaking) — `message` replaced by request facts, and the `reviewLogFieldMaxWidth` bound |
|
|
|
|
## Development
|
|
|
|
```bash
|
|
pnpm run check # Type-check TypeScript (no emit)
|
|
pnpm run lint # Biome + ESLint + lint:md
|
|
pnpm run lint:md # rumdl on README and docs
|
|
pnpm run test # Run tests from ./test
|
|
pnpm run test:watch # Run tests in watch mode
|
|
```
|
|
|
|
### Pre-commit hooks
|
|
|
|
This project uses [prek](https://prek.j178.dev/) to run Biome, ESLint, and rumdl on staged files before each commit.
|
|
Run `pnpm install` to set up hooks automatically.
|
|
|
|
## Acknowledgments
|
|
|
|
This project began as a fork of [MasuRii/pi-permission-system](https://github.com/MasuRii/pi-permission-system).
|
|
Thank you to [MasuRii](https://github.com/MasuRii) for the original work that made this possible.
|
|
|
|
Thank you to the [OpenCode](https://opencode.ai) team for the permission model design that inspired the flat config format and evaluation semantics used in this extension.
|
|
|
|
## License
|
|
|
|
[MIT](LICENSE)
|