Permission rules
A permission rule names a tool and optionally narrows which calls to it match,
in one of three forms: the bare tool, tool(argGlob), or
tool(operation:argGlob). The rule everyone gets wrong is precedence — rules
are not applied in the order you added them. deny beats ask, and ask
beats allow, so a broad allow can never re-enable what an explicit deny covers.
A rule names a tool and, optionally, narrows which calls to that tool it matches. Add the narrowest rule that supports a task you have actually tested:
cmdop permissions allow 'execute_command(git status)'
cmdop permissions ask 'execute_command(git diff *)'
cmdop permissions deny 'execute_command(rm *)'
cmdop permissions listQuote rules containing spaces or shell metacharacters so your shell passes them to Cmdop unchanged.
The three forms
| Form | Matches | Example |
|---|---|---|
tool | Every call to that tool. | file_write |
tool(argGlob) | Calls whose main argument matches the glob. | execute_command(git *), file_write(/app/**) |
tool(operation) or tool(operation:argGlob) | One operation of a tool that dispatches several, optionally narrowed by a glob. | connect(exec), connect(exec:prod-*) |
* matches within one path or command segment; ** crosses separators, which
is why file_write(/app/**) covers a whole tree while file_write(/app/*) does
not.
Precedence: deny beats ask beats allow
Rules are not evaluated in the order you added them. deny wins over ask,
and ask wins over allow — always. This is the behavior people most often
get wrong.
Two consequences worth internalizing:
- A broad allow can never re-enable something an explicit deny covers, so a deny for a high-risk pattern is safe to leave in place permanently.
- Adding an
askon top of an existingallowdoes downgrade that path to a prompt — theaskwins.
Beneath all of it sits the protected floor, which no rule and no mode can override.
Add and remove a rule
cmdop permissions allow 'execute_command(git *)' \
--reason 'Reviewed read-only Git operations'
cmdop permissions list
cmdop permissions revoke 'execute_command(git *)'revoke expects the exact raw rule text shown by list, not an equivalent
pattern. Review the list after every change, especially when automation
generates rules.
Keep patterns honest
A pattern will match more than the example that motivated it. Before you commit a rule:
- prefer an exact command or path over a
*; - keep explicit denies for high-risk actions rather than relying on the absence of an allow;
- test against a non-destructive operation first, then confirm with
cmdop permissions auditthat the rule you expected is the rule that matched.
Common questions
How do I write a Cmdop permission rule?
Name the tool, then optionally narrow its argument with tool(argGlob) or
tool(operation:argGlob). Quote the rule so your shell passes spaces and
metacharacters to Cmdop unchanged.
Which permission rule wins?
deny wins over ask, and ask wins over allow. Rule precedence is not the
order you added rules in, so a broad allow cannot reopen something an explicit
deny covers.
How narrow should an allow rule be?
As narrow as the tested task allows. Prefer exact commands and paths over broad
globs, add a reason, then confirm the matched rule with cmdop permissions audit.