agentperm
One permission policy for Claude Code, Codex CLI, OpenCode, Gemini CLI, and Kiro, with a shell parser that reads compound commands the way bash does.
The problem
Coding agents ask before running shell commands. Each has its own permission config, and none of them parse what you typed — cat foo | head -60 is one opaque string, so every agent asks about it even though both sides are read-only. You end up maintaining the same rules in three different places, and still getting prompted for things you've already allowed.
What agentperm does
It replaces those separate configs with a single .agent-permissions.jsonc that every agent consults. It parses pipes, &&/|| chains, loops, subshells, redirects, and bash -c wrappers, then decides each segment against your rules:
{
"version": 1,
"permissions": {
"allow": [
"Shell(git {status,log,diff,show,branch} !-*)",
"Shell(sed !{-i,--in-place})",
"Shell({cat,head,grep,rg,ls,wc})",
"Python(readonly)",
"Read", "Grep"
],
"ask": [
{ "tool": "Bash", "command": ["sed"], "when": { "hasOption": ["-i"] },
"reason": "sed -i edits files in place" }
],
"deny": ["Shell(sudo)", "Shell(rm -r -f /*)"]
},
"shell": {
"redirection": {
"stdoutToFile": "ask",
"allowPaths": ["/tmp"]
}
}
}
| Command | Verdict | Why |
|---|---|---|
cat foo 2>&1 | head -60 |
allow | both segments allowed; 2>&1 is a safe fd dup |
git status && cat README.md |
allow | && — both sides allowed |
sed -i s/old/new/ x.txt |
ask | the ask rule on -i wins over the allow |
echo hi > /tmp/out.txt |
allow | redirect target matches allowPaths |
echo hi > ~/notes.txt |
ask | redirect to file outside allowPaths |
cat foo | ./deploy.sh |
ask | one unrecognized segment in a compound |
rm -rf / |
deny | deny always wins |
No prompt on the first two. Native matchers ask about both because a pipe or && defeats string matching.
Install
uv tool install agentperm # or: pipx install agentperm
Setup
agentperm install # wire hooks into Claude Code / Codex / OpenCode / Gemini / Kiro
agentperm import # pull existing native rules into ~/.agent-permissions.jsonc
agentperm edit # open the policy in $VISUAL/$EDITOR
install writes to each agent's hook config, or merges into ~/.rulesync/hooks.json if you use Rulesync. Preview with --dry-run. Your native settings keep working underneath — nothing is taken away.
Writing rules
Rules go in allow, ask, or deny. First match wins within a list; deny beats ask beats allow across lists.
Shell patterns — the recommended form. Flags float; trailing args open by default:
"Shell(git {status,log,diff} !-*)" // match subcommands, reject all flags
"Shell(git push !--force)" // allow push, forbid --force
"Shell(aws values(--region) ec2 describe-*)" // declare that --region eats the next token
"Shell(git stash {list,show} !... !-*)" // exact subcommand, no extra args or flags
Per-rule redirect paths — allow redirects to specific directories for matching commands:
{"Shell(mise exec just synth-env)": {"allowPaths": ["/tmp"]}}
Named tools — match non-shell tools, optionally scoped by input:
"Read" // exact
"mcp__memory__*" // prefix glob
"WebFetch(domain:github.com)" // URL host scoping
"Edit(src/**)" // path glob scoping
Legacy positional — "Bash(git status:*)" remains supported for existing policies and imports.
Flag matching — { "tool": "Bash", "command": ["sed"], "when": { "hasOption": ["-i"] }, "reason": "..." }.
Python analysis — "Python(readonly)" shallowly inspects python -c and heredoc source with the stdlib AST. Imports, printing, and inspection run without a prompt; mutation asks.
In compounds the strictest segment decides. One unrecognized command = ask. Deny always wins. Full spec: Shell pattern DSL · Policy reference.
Global + per-project
~/.agent-permissions.jsonc sets global defaults. <repo>/.agent-permissions.jsonc adds project-specific rules. Both apply at the same time — deny wins across both.
// global — broad defaults
{ "version": 1, "permissions": {
"allow": ["Shell({cat,ls,grep,rg})", "Shell(git {status,diff,log})", "Read"],
"deny": ["Shell(sudo)", "Shell(rm -r -f /*)"]
}}
// ~/work/payments/.agent-permissions.jsonc — project overrides
{ "version": 1, "permissions": {
"allow": ["Shell(pytest)", "Shell(pnpm run {build,test,lint})"],
"deny": [{ "tool": "Bash", "command": ["git"],
"when": { "hasOption": ["--force"] },
"reason": "no force-push in this repo" }]
}}
A repo adds its own tools and clamps down on dangerous variants without touching the global file. agentperm edit --local creates or opens the project file.
Redirect allowlisting
File redirects (>, >>) default to ask. You can allowlist directories globally or per-rule:
"shell": {
"redirection": {
"stdoutToFile": "ask",
"allowPaths": ["/tmp", "/private/tmp/claude-*"]
}
}
Paths resolve through symlinks (/tmp covers /private/tmp on macOS). Globs match per component. Relative targets resolve against the working directory.
Zellij pane bypass
The bundled WASM plugin adds a per-pane toggle: ask and unmatched commands become allow in that pane only. deny rules still bite. Under Claude Code's own --dangerously-skip-permissions, agentperm steps aside completely.
What it doesn't do
- No sandbox. It decides allow / ask / deny; it doesn't contain what runs.
- No replacing native settings. Those keep working as a fast path.
- No MCP server management. Use Rulesync or native config for that.
More
Policy reference · Shell pattern DSL · Architecture · Adapter notes · Changelog
License
MIT
Metadata
Release files for agentperm 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| agentperm-0.3.0.tar.gz | 88.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentperm-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 149.5 kB
Release files / agentperm-0.3.0.tar.gz
| Download URL | agentperm-0.3.0.tar.gz |
|---|---|
| Size | 88.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba7a732de2c45c6100e7d4b65fad3babb885aff1c34d0e7a79ce4313e2beabad
|
|
BLAKE2b-256 checksum How to use checksums |
8818cad2cea66f575e1fa04e6fd39c2cd67e8d50d6a9ee305b94ca17bcdbb571
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / agentperm-0.3.0-py3-none-any.whl
| Download URL | agentperm-0.3.0-py3-none-any.whl |
|---|---|
| Size | 60.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d3f45b3f08adf08d0d0746ca1c94cc4afdf78f7398ef594c2baf80095841cd26
|
|
BLAKE2b-256 checksum How to use checksums |
eab9930e5e688a4a9ee4fe5e18ffdbc75946aa423b15f1e22d35b8232d079653
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for agentperm 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.