pi-permission-gate
Config-driven permission system for Pi agents. Deny-by-default tool restriction with glob matching, path normalization, self-protection, and structured logging.
Package details
Install pi-permission-gate from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-permission-gate- Package
pi-permission-gate- Version
0.1.3- Published
- Jun 12, 2026
- Downloads
- 734/mo · 239/wk
- Author
- juanjeojeda
- License
- MIT
- Types
- extension
- Size
- 15.9 KB
- Dependencies
- 1 dependency · 5 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-permission-gate
Config-driven permission system for Pi agents. Deny-by-default tool restriction with glob matching, path normalization, self-protection, and structured logging.
Install
From npm:
pi install npm:pi-permission-gate
From git:
pi install git+https://github.com/juanje/pi-permission-gate.git
Or load directly without installing:
pi -e /path/to/pi-permission-gate/extensions/permission-gate.ts
Quick start
Create .pi/permissions.json in your project:
{
"defaultMode": "deny",
"permissions": {
"tools": {
"read": { "paths": { "allow": ["**"] }, "default": "allow" },
"write": { "paths": { "allow": ["tmp/*"] }, "default": "deny" },
"bash": {
"allow": ["date", "date *", "git *", "bin/*"],
"deny": ["rm *", "curl *", "wget *", "*.env*"],
"default": "deny"
}
}
}
}
Optional local overrides in .pi/permissions.local.json (gitignored).
How it works
The extension hooks into three Pi events:
session_start— loads and merges policy files, initializes the permission logbefore_agent_start— hides tools blocked at session level viasetActiveTools()tool_call— enforces granular rules before each tool executes
Evaluation order
For each tool call:
- Self-protection — blocks operations on
.pi/permissions.json,.pi/permissions.local.json, and.pi/extensions/(hardcoded, cannot be overridden) - Simple rules —
"allow"or"deny"for the entire tool - Deny patterns — command/path glob patterns (deny always wins)
- Allow patterns — command/path glob patterns
- Tool default — per-tool
defaultmode - Policy default — global
defaultMode
Glob matching
Patterns use * (any sequence) and ? (single character). Tilde expansion is supported in patterns (~/.ssh/*). Paths are normalized (~, ./, ../, absolute → relative) before matching.
Secret redaction
Logged inputs and reasons are automatically scanned for credentials before writing. Two layers of detection:
- Vendor prefixes (via secret-sniff) — AWS
AKIA*, GitHubghp_*, GitLabglpat-*, Anthropicsk-ant-*, OpenAIsk-proj-*, Slackxoxp-*, Stripe, JWT, PEM, and more - Variable name patterns — catches
SECRET=,_KEY=,_TOKEN=,PASSWORD=,CREDENTIAL=,AUTH=,DATABASE_URL=,REDIS_URL=, andBearerheaders regardless of value format
Structured log
Each decision is appended to .pi/logs/permission-gate_{timestamp}.jsonl (secrets redacted):
{"ts":"2026-06-09T00:05:16.733Z","tool":"bash","input":"cat .env","decision":"deny","reason":"'cat .env' matches deny pattern for bash"}
Custom log directory via logPath in the policy:
{
"defaultMode": "deny",
"logPath": "var/audit",
"permissions": { "tools": {} }
}
Policy format
interface Policy {
defaultMode: "allow" | "deny";
logPath?: string; // optional, relative to project root
permissions: {
tools: Record<string, "allow" | "deny" | ToolRule>;
};
}
interface ToolRule {
default?: "allow" | "deny";
allow?: string[]; // glob patterns (bash commands, etc.)
deny?: string[];
paths?: {
allow?: string[];
deny?: string[];
};
}
Merge strategy
When both .pi/permissions.json and .pi/permissions.local.json exist:
defaultMode— local wins- Tool rules — deep merged (arrays concatenated)
- Local string rule (
"allow"/"deny") — overrides base entirely
Development
git clone https://github.com/juanje/pi-permission-gate.git
cd pi-permission-gate
npm install
npm run check # typecheck + lint + test
License
MIT — see LICENSE.