What are Permission Rules?
A permission rule has the form Tool or Tool(specifier) and comes in three kinds: allow, ask and deny. A bare tool name such as Bash matches every use of that tool; a specifier narrows it, for example Bash(npm run build) matches that exact command, Read(./.env) matches reading that file, and WebFetch(domain:example.com) matches fetches to that domain. Deny and ask rules can also match a top-level input parameter with Tool(param:value).
Why do they exist?
They give you a middle ground between asking every time and allowing everything: routine safe commands stop prompting while dangerous ones are always refused. Rules can be checked into version control for a team, and each developer can add their own. Precedence is explicit: a deny at any scope beats allow, and managed settings are highest, so not even command-line arguments can override a managed rule.
How do they work, and what are the limits?
Bash rules match the command text Claude writes: * stands for any text, and a trailing space-star also matches the bare command; in compound commands (&&, ||, ;, pipes) each subcommand must match an allow rule separately, while a deny or ask rule fires if any subcommand matches; a fixed set of wrappers such as timeout, time, nice and nohup is stripped before matching. The docs say plainly that a deny or ask rule covers the invocation Claude usually produces and is not a security boundary around the program: Bash(rm *) does not stop /bin/rm or bash -c. For enforcement independent of text use the Sandbox; for custom checks use a PreToolUse hook.
So what does this mean for me?
Treat rules as a convenience layer that reduces prompts, not a guarantee that something cannot happen. Write allow rules specific enough to include the inner command (use Bash(devbox run npm test), not Bash(devbox run *), which lets anything after devbox run through); leave what truly must not happen to the Sandbox or a hook. This "how to use it" part is my summary of the official docs.
The docs' example: allow Bash(npm run *) and Bash(git commit *) and deny Bash(git push *), so npm scripts and commits stop prompting and git push is refused; yet git -C . push is not matched by that deny.
Pros: fine-grained, shareable, with explicit precedence. Cons: they match text only, so alternative spellings and environment runners make a rule wider or narrower than you think.