refactor(permission-system): simplify policy and autoreview flow

This commit is contained in:
云服务部-叶林立
2026-09-03 02:01:06 +08:00
parent 180da95ab7
commit 6e7effa792
112 changed files with 911 additions and 10346 deletions
+17 -59
View File
@@ -11,22 +11,17 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
> **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.
> **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. my-pi uses one global ordinary policy and a separate non-overridable hard-guard file; explicitly configured authorizers may approve any ordinary-policy `ask`, including `path`.
## 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
- **Enforces `allow` / `ask` / `deny`** at tool-call time with UI confirmation dialogs
- **Uses one global ordinary policy file**; project `policy.jsonc` files are not loaded
- **Chooses the most specific matching pattern**, so a specific deny wins over a broad `"*": "allow"` regardless of declaration order
- **Applies a separate hard-guard policy first** so session grants, per-agent rules, and model review cannot override high-risk denies
- **Controls Bash, paths, MCP, skills, and tool surfaces** while leaving active-tool ownership entirely to Tool Search
- **Reviews ordinary `ask` requests automatically** with integrated AutoReview; its `allow` / `deny` verdict is final for ordinary tools and `path`, while `defer` reaches the next authorizer or human terminal
- **Forwards subagent `ask` decisions** to the parent authority and keeps structured audit logs
## Install
@@ -78,66 +73,29 @@ This lets you express rules like "allow reads but deny `.env` files" at the indi
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`.
Paths outside the working tree have no separate boundary gate. Configure them with the same `path`, per-tool, and Bash patterns used for in-project paths. These layers still compose with most-restrictive-wins.
See [docs/configuration.md](docs/configuration.md) for the full recipe.
## Configuration
The integrated extension uses two independent JSONC files:
The integrated extension uses two independent global 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` |
| Ordinary policy, runtime knobs, AutoReview | Global only | `~/.pi/agent/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.
Project-level `policy.jsonc` files are ignored. Project agent Markdown remains a separate per-agent restriction mechanism.
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:
The bundle seeds missing files with mode `0600`. During `update.sh`, only the exact known legacy default policy is backed up and replaced by the current bundled seed. Any customized or invalid policy is preserved and reported instead of being guessed or overwritten.
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`.
Within a pattern map, the most specific match wins: exact patterns, then more literal characters, fewer wildcards, and longer patterns. Declaration order is only a tie-breaker.
`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`.
There is no `external_directory` gate. Outside-CWD paths use the same configured tool, `path`, and Bash rules as other paths.
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.
Tool Search alone owns Pi's active tool list. Permission System only decides whether an invoked tool call is allowed, denied, or requires confirmation.
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.
See [docs/configuration.md](docs/configuration.md) for the complete reference and [docs/hard-guards.md](docs/hard-guards.md) for the independent hard-guard format.
## Documentation