# @enne2/pi-policy-gate A standalone **pi** extension package that applies a practical policy layer to risky tool calls. It classifies operations into four outcomes: - **allow** — execute immediately - **confirm** — require human approval - **deny** — block outright - **refine** — block and tell the model how to reformulate the command safely The default policy is opinionated but usable, and it is now **baked into the code** even if you provide no config file. Built-in defaults: - `sudo` and `ssh` are **not denied by default** - risky commands are generally **confirmed** - only objectively dangerous or ambiguous forms are **denied/refined** - destructive deletes should use an **explicit absolute path** - `curl` / `wget` / `nc` / `socat` / `telnet` are **confirmed** - `env` / `printenv` are **confirmed** - bash-based writes to sensitive files such as `AGENTS.md`, `.env`, shell dotfiles, SSH config, and local policy files are **confirmed** - programmatically generated local **alert sounds** play on confirmation requests and blocks/refinements by default ## What it protects against Examples of behavior it catches by default: - `rm -rf *` - `rm -rf .` - `rm -rf ..` - `rm -rf /` - `find . -delete` - `sudo ...` (confirmation) - `ssh ...` / `scp` / `rsync` / `sftp` (confirmation) - `curl ...`, `wget ...`, `nc ...`, `socat ...` (confirmation) - `env`, `printenv` (confirmation) - `git reset --hard`, `git clean -fdx`, `git push` (confirmation) - `sed -i ~/.bashrc ...` or `tee AGENTS.md` (confirmation) - writes outside the workspace (confirmation) - reads/writes to sensitive paths such as `~/.ssh`, `AGENTS.md`, or `.env` (confirmation) ## Install ### Directly in pi from git Private Gitea/GitHub style install: ```bash pi install 'ssh://git@git.enne2.net:222/enne2/pi-policy-gate.git' ``` Or with HTTPS: ```bash pi install https://git.enne2.net/enne2/pi-policy-gate.git ``` ### With npm from the git repository ```bash npm install git+ssh://git@git.enne2.net:222/enne2/pi-policy-gate.git ``` Then load it from local `node_modules` or publish it to your npm registry later. ## Sound alerts The package ships with three pre-generated WAV files and the extension only plays them at runtime: - `confirm.wav` — soft ascending alert for human confirmation - `block.wav` — lower, harsher alert for denied actions - `refine.wav` — distinct mid-tone alert for “blocked, but reformulate safely” cases By default it plays the bundled files from the package `assets/` directory, so it does **not** write audio files into your agent home at startup. It automatically uses the first available local player among: - `paplay` - `pw-play` - `aplay` - `ffplay` - `play` You can also test them manually inside pi: - `/policy-gate-sound` - `/policy-gate-sound block` - `/policy-gate-sound refine` ## Config files The extension looks for config files in this order: 1. built-in defaults 2. `~/.pi/agent/policy-gate.json` 3. extra file passed with `--policy-gate-config /path/to/file.json` 4. `.pi/policy-gate.json` in the current project Project config overrides global config. ## Example config Copy and adjust: ```bash cp policy-gate.example.json ~/.pi/agent/policy-gate.json ``` Example: ```json { "workspaceRoots": ["."], "requireAbsolutePathForRecursiveDelete": true, "soundEnabled": true, "soundConfirmEnabled": true, "soundBlockEnabled": true, "soundRefineEnabled": true, "soundPlayer": "auto", "overrides": [ { "tool": "bash", "commandRegex": "^sudo systemctl restart my-safe-service$", "action": "allow" } ] } ``` ## Config reference ```json { "workspaceRoots": ["."], "requireAbsolutePathForRecursiveDelete": true, "requireAbsolutePathForFindDelete": true, "confirmSensitiveReads": true, "confirmSensitiveWrites": true, "confirmWritesOutsideWorkspace": true, "soundEnabled": true, "soundConfirmEnabled": true, "soundBlockEnabled": true, "soundRefineEnabled": true, "soundPlayer": "auto", "soundDirectory": "/absolute/path/to/custom/policy-gate-sounds", "sensitivePathGlobs": ["~/.ssh/**", "**/.env"], "overrides": [ { "id": "optional label", "tool": "bash", "commandRegex": "^ssh deploy@staging\\b", "pathGlob": "~/.ssh/**", "action": "allow | confirm | deny | refine", "reason": "Shown to the user / model", "suggest": "Only used with refine/deny to explain the safer alternative" } ] } ``` ### Notes - `soundPlayer: "auto"` picks the first available local player. - Set `soundEnabled: false` to disable all alert sounds. - `soundConfirmEnabled`, `soundBlockEnabled`, and `soundRefineEnabled` let you mute just one class of event. - `soundDirectory` is optional: by default the extension uses the package's bundled `assets/` directory. If you set it, it should point to an existing directory containing `confirm.wav`, `block.wav`, and `refine.wav`. - `workspaceRoots` can be absolute paths, `~` paths, or paths relative to the current project cwd. - `commandRegex` is evaluated against the raw bash command string. - `pathGlob` is matched against both the absolute path and the path relative to the current cwd. - last matching override wins. ## Behavior model ### Allow Low-risk reads and normal project-local writes are allowed. ### Confirm Potentially dangerous but legitimate actions prompt the human. Examples: - `sudo systemctl restart nginx` - `ssh deploy@staging 'systemctl status api'` - `rm -rf /full/path/to/some/build-cache` ### Deny / Refine Only clearly unsafe or ambiguous forms are blocked. Examples: - `rm -rf *` - `rm -rf .` - `find . -delete` The extension tells the model to switch to a safer form such as using an explicit absolute path. ## Useful commands Inside pi: - `/policy-gate` — show current policy summary - `/policy-gate-sound` — play the confirmation sound - `/policy-gate-sound block` — play the deny sound - `/policy-gate-sound refine` — play the refine sound ## Development Install deps, regenerate the packaged WAV assets if needed, and verify the tarball: ```bash npm install npm run generate:sounds npm run pack:check ``` Try it without installing globally: ```bash pi -e /absolute/path/to/pi-policy-gate --list-models ``` ## Packaging notes This package is intentionally TypeScript-only and relies on pi's built-in runtime loader. No build step is required.