---
slug: "pi-vault-pi-guardrails"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/pi-vault/pi-guardrails@master/README.md"
repo: "https://github.com/pi-vault/pi-guardrails"
source_file: "README.md"
branch: "master"
---
# @pi-vault/pi-guardrails

[![npm version](https://img.shields.io/npm/v/%40pi-vault%2Fpi-guardrails)](https://www.npmjs.com/package/@pi-vault/pi-guardrails)
[![Quality](https://github.com/pi-vault/pi-guardrails/actions/workflows/quality.yml/badge.svg?branch=master)](https://github.com/pi-vault/pi-guardrails/actions/workflows/quality.yml)
[![Node >=22.19.0](https://img.shields.io/badge/node-%3E%3D22.19.0-339933)](https://nodejs.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/pi-vault/pi-guardrails/blob/master/README.md#license)

Damage control for the Pi coding agent. Protects sensitive files, enforces workspace boundaries, and gates dangerous commands so a runaway tool call never wrecks your day.

## Install

```sh
pi install npm:@pi-vault/pi-guardrails
```

Reload Pi after install:

```text
/reload
```

To try a local checkout before publishing:

```sh
pi -e /absolute/path/to/pi-guardrails
```

## Quick Start

pi-guardrails works out of the box — no configuration needed.

Any tool call the agent (or a `!` user command) tries to make goes through three checks: **sensitive paths**, **workspace boundary**, and **dangerous commands**. Each check produces a three-state decision:

- **Allow** — run silently.
- **Ask** — show a three-button dialog: `Allow once`, `Allow for session`, `Deny`.
- **Deny** — block with a clear reason.

`Allow for session` remembers the decision for the same action for the rest of the session, so repeated runs of the same safe command don't keep prompting.

## What it does

- **Sensitive file protection** — blocks reads and writes to `.env`, `.env.*`, `.dev.vars`, `.git/**`, `~/.ssh/**`, `~/.aws/**`, and `~/.gnupg/**`. Exception patterns allow `.env.example`, `.env.test`, `*.example.env`, and `*.pub` SSH keys.
- **Workspace boundary enforcement** — file actions that target paths outside the current working directory prompt for confirmation by default.
- **Bash command allowlist** — known-safe commands (`ls`, `cat`, `git status`, `git log`, `npm install`, etc.) run without prompting.
- **Bash command denylist** — known-dangerous patterns (`rm -rf /`, `mkfs`, `dd if=/dev/zero`, fork bomb, etc.) are always blocked.
- **Shell composition detection** — commands using `&&`, `||`, `;`, `|`, `>`, `>>`, `<`, `<<`, `$(...)`, or backticks prompt for confirmation.
- **Bash path extraction** — sensitive files are blocked even when accessed via an allowlisted command (e.g. `cat .env`, `grep ~/.ssh/id_rsa`).
- **Symlink resolution** — file paths are canonicalized before rules run, so a symlink inside the workspace pointing to `~/.aws/credentials` is still blocked.
- **Session-scoped approvals** — once you approve an action for the session, the same action bypasses checks for the rest of the session.
- **Fail-closed** — when no interactive UI is available, `Ask` decisions block instead of silently allowing. Rule errors are treated as dangerous.
- **Three-state decision model** — `sensitive-path` and `dangerous-command` always deny. `outside-workspace` and `shell-composition` follow your policy (`ask` or `deny`). Everything else asks.

## Default Behavior

| Action | Default |
| --- | --- |
| Reading or writing a sensitive file (`.env`, `~/.ssh/`, `~/.aws/`, `.git/`, `~/.gnupg/`) | **Deny** |
| Running a known-dangerous command (`rm -rf /`, `mkfs`, fork bomb, etc.) | **Deny** |
| Accessing a file or directory outside the current workspace | **Ask** |
| Running a shell command with `&&`, `\|`, `>`, `$()`, backticks, etc. | **Ask** |
| Running a command not in the safe-command list (e.g. `git commit`, `npm publish`) | **Ask** |
| Running a safe command (`ls`, `cat`, `git status`, `npm install`, etc.) | **Allow** |

## What It Protects

### Always denied paths

- **Environment files** — `.env`, `.env.local`, `.env.production`, `.dev.vars`.
  Exception: `.env.example`, `.env.test`, `.env.sample`, `*.example.env`.
- **Git internals** — everything under `.git/`.
- **SSH keys** — `~/.ssh/id_rsa`, `~/.ssh/config`, etc.
  Exception: `*.pub` (public keys).
- **AWS credentials** — everything under `~/.aws/`.
- **GnuPG keys** — everything under `~/.gnupg/`.

### Always denied commands

- `rm -rf /`, `rm -rf ~`, `rm -rf /*`
- `:(){ :|:& };:` (the fork bomb)
- `mkfs`, `dd if=/dev/zero`, `dd if=/dev/random`
- `> /dev/sda`, `shred`, `wipefs`, `blkdiscard`

## Configuration

Create a config file to override the defaults. The default location is resolved by your pi settings. All fields are optional — missing ones fall back to built-in defaults.

```json
{
  "enabled": true,
  "protectedPaths": [
    {
      "id": "env-files",
      "enabled": false
    }
  ],
  "bashAllowlist": ["my-custom-readonly-tool"],
  "bashDenylist": ["my-custom-evil-command"],
  "outsideWorkspacePolicy": "ask",
  "shellOperatorPolicy": "ask",
  "debug": false
}
```

### Top-level fields

- `enabled` — set to `false` to disable every check without uninstalling. Default: `true`.
- `protectedPaths` — array of rules matched against file paths. Override a default rule by its `id` or append a new one. See [Protected path rules](#protected-path-rules).
- `bashAllowlist` — array of single-word or multi-word prefixes. `git status` matches `git status --short` but not `git statusbar`. Merged with the default allowlist. Default: ~95 safe commands.
- `bashDenylist` — array of substring patterns matched against the full command. Merged with the default denylist. Default: 11 dangerous patterns.
- `outsideWorkspacePolicy` — what to do when a file action targets a path outside the current directory. `"ask"` (default) or `"deny"`.
- `shellOperatorPolicy` — what to do when a bash command uses `&&`, `|`, `>`, etc. `"ask"` (default) or `"deny"`.
- `debug` — when `true`, logs every `Ask` decision to `console.debug` in addition to the default `console.warn` for blocks. Default: `false`.

### Protected path rules

```json
{
  "id": "my-secret-files",
  "description": "Project-specific secrets",
  "patterns": [{ "pattern": "secrets/**" }],
  "allowedPatterns": [{ "pattern": "secrets/public/**" }],
  "enabled": true
}
```

- `id` — stable identifier used to override or disable a default rule by `id`.
- `description` — human-readable label shown in block reasons.
- `patterns` — array of file or directory patterns to protect.
- `allowedPatterns` — optional exceptions (e.g. `.env.example`).
- `enabled` — set to `false` to skip this rule. Default: `true`.

Patterns support glob (`*`, `**`) by default and case-insensitive regex when `"regex": true`. A pattern with no `/` matches the basename anywhere; a pattern with `/` matches the full path. A leading `~` is expanded to your home directory.

Invalid patterns (bad regex, empty string) disable just that rule and log a warning — they never throw or silently disable the whole extension.

## Disable All Checks

Set the master switch:

```json
{ "enabled": false }
```

Or disable a single rule by id:

```json
{
  "protectedPaths": [
    { "id": "env-files", "enabled": false }
  ]
}
```

## Compatibility

- Node `>=22.19.0`
- Peer dependencies: `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`
- Intended for Pi sessions with package and extension support

## Development

```sh
pnpm install
pnpm check
pnpm pack --dry-run
```

## License

MIT
