pi-permission-gate
v0.1.4
Published
Config-driven permission system for Pi agents. Deny-by-default tool restriction with glob matching, path normalization, self-protection, and structured logging.
Maintainers
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-gateFrom git:
pi install git+https://github.com/juanje/pi-permission-gate.gitOr load directly without installing:
pi -e /path/to/pi-permission-gate/extensions/permission-gate.tsQuick 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 + testLicense
MIT — see LICENSE.
