pi-secure-it
In-process tool guard and subagent posture hardening for Pi coding agent. Enforces filesystem allow/deny lists and domain allowlists on read, write, edit, fetch_content, web_search, and get_search_content — with interactive ask-tier prompts to grant acces
Package details
Install pi-secure-it from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-secure-it- Package
pi-secure-it- Version
1.1.0- Published
- Jul 19, 2026
- Downloads
- 116/mo · 116/wk
- Author
- hannesro
- License
- MIT
- Types
- extension, skill
- Size
- 1.6 MB
- Dependencies
- 1 dependency · 1 peer
Pi manifest JSON
{
"extensions": [
"./security-guard.ts",
"./sandbox/index.ts"
],
"skills": [
"./skills"
],
"image": "https://raw.githubusercontent.com/hannesro/pi-secure-it/main/banner.png"
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-secure-it
Two-layer security extension for the Pi coding agent:
- Layer 1 — OS-level bash sandbox (
sandbox-execon macOS,bubblewrapon Linux) that blocks filesystem writes outside an allow-list, reads of sensitive paths, and network egress to unlisted domains. - Layer 2 — In-process tool guard applying the same policy to the tools the OS sandbox can't reach:
read,write,edit,fetch_content,web_search,get_search_content. - Layer 3 — Subagent posture: optionally drop or restrict network access when running headless (
-p, JSON mode, subagents).
When a tool call is blocked you get an interactive prompt — no need to leave pi and hand-edit config files. Choose this once, always for this project, or always for all projects (file or parent-folder granularity). Decisions are persisted and audited.
Install
# From npm (once published)
pi install npm:pi-secure-it
# From git
pi install git:github.com/hannesro/pi-secure-it
# Try without installing
pi -e git:github.com/hannesro/pi-secure-it
Requirements
- macOS or Linux
- macOS:
sandbox-execis built in - Linux:
bubblewrap,socat
Configuration
Policy files are merged in order:
| File | Scope |
|---|---|
~/.pi/agent/extensions/sandbox.json |
Global (all projects) |
<cwd>/.pi/sandbox.json |
Project-local (auto-written by ask-tier prompts) |
Copy sandbox.example.json from this package as a starting point for your global config.
Key fields
{
"enabled": true, // set false to disable Layer 2 without --yolo
"network": {
"allowedDomains": [ // domains fetch_content / get_search_content may reach
"github.com", "*.github.com",
"registry.npmjs.org"
],
"deniedDomains": [] // explicit block-list (checked before allowedDomains)
},
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"], // Layer 1 + Layer 2: no read at all
"modelDenyRead": ["~/.netrc"], // Layer 2 only: model's read tool blocked; subprocesses ok
"allowWrite": [".", "/tmp"], // Layer 2: writes only inside these roots
"denyWrite": [".env", "*.pem"] // Layer 1 + Layer 2: write always blocked
},
"subagent": {
"network": "allow" // "allow" | "deny" | "research-only"
}
}
Absolute-deny tier
Access to ~/.ssh, ~/.gnupg, ~/.aws, *.pem, *.key is always a high-risk block. The prompt requires typing "i understand" verbatim and the "always" option is never offered.
Commands
| Command | Description |
|---|---|
/security |
Show Layer 2 policy, project-local overrides, last 10 audit events |
/sandbox |
Show Layer 1 (bash sandbox) config |
/sandbox reload |
Live-reload sandbox after manual edits to sandbox.json |
Escape hatches
pi --yolo # disables ALL layers globally (visible warning banner)
pi --no-sandbox # alias for --yolo
Audit log
Every block/allow/always decision is appended to ~/.pi/agent/audit.log as a JSON line:
{
"ts": "2026-07-15T10:00:00.000Z",
"layer": 2,
"tool": "write",
"subject": "/Users/you/project/secret.pem",
"reason": "denyWrite matched \"*.pem\"",
"decision": "no",
"cwd": "/Users/you/project"
}
Development
git clone https://github.com/hannesro/pi-secure-it
cd pi-secure-it
npm install # installs typescript for typecheck
npm run typecheck # type-checks against pi's bundled .d.ts files
pi -e . # load extension for the current session only
Releasing
Releases are fully automated with semantic-release, driven by Conventional Commits on main:
fix: ...→ patch releasefeat: ...→ minor releasefeat!: ...or aBREAKING CHANGE:footer → major releasechore:,docs:,refactor:,test:,ci:etc. → no release by themselves
Commit messages on pull requests are checked by commitlint (.github/workflows/commitlint.yml). On every push to main, .github/workflows/release.yml runs semantic-release, which:
- Determines the next version from commits since the last release.
- Generates release notes and prepends them to
CHANGELOG.md. - Publishes to npm (
npm publish --provenance) and bumpspackage.json. - Creates the
vX.Y.Zgit tag and GitHub release. - Commits
CHANGELOG.md/package.jsonback tomain(chore(release): ... [skip ci]).
Nothing to run locally beyond writing conventional commit messages — just merge to main. Publishing uses npm Trusted Publishing (OIDC) — no NPM_TOKEN secret required. One-time setup on npmjs.com:
- Go to the package's Settings → Trusted Publisher on npmjs.com.
- Select GitHub Actions and configure: organization/user
hannesro, repositorypi-secure-it, workflow filenamerelease.yml, allowed actionnpm publish. - (Recommended) Under Settings → Publishing access, choose "Require two-factor authentication and disallow tokens" to disable classic token-based publishing entirely, and revoke any automation tokens you previously created.
GITHUB_TOKEN is provided automatically by Actions; the id-token: write permission in release.yml is what lets npm's OIDC exchange work.
License
MIT
