Claude Code permission rules live in settings.json (also CLI flags, subagent frontmatter, hook matchers). Format: ToolName(specifier). A bare tool name in deny removes the tool entirely.
{
"permissions": {
"allow": ["Bash(pnpm test *)", "WebFetch(domain:docs.astro.build)"],
"deny": ["Read(./.env*)", "Read(~/.ssh/**)", "Read(~/.aws/**)", "WebSearch"]
}
}
| Rule | Applies to | Matches |
|---|---|---|
Bash(npm run *) | Bash, Monitor | Command patterns |
Read(~/secrets/**) | Read, Grep, Glob, LSP | Path patterns |
Edit(/src/**) | Edit, Write, NotebookEdit | Path patterns |
WebFetch(domain:example.com) | WebFetch | Domains |
Agent(Explore) | Agent | Subagent types |
An Edit(...) allow also grants Read on the same path. Read/Edit deny rules cover file commands Claude Code recognizes inside Bash (cat, sed, grep), but not arbitrary subprocesses - a Python script that opens the file walks past them. Close that gap with the sandbox ladder.
Rules are evaluated before the permission mode, so a deny entry still blocks under bypassPermissions. That ordering is what makes a rule worth writing instead of prompting - see two-way door for deciding which actions earn one.
See the read/mutate permission split for which of the 42 tools ask. Full security playbook: Claude Code security in 2026. Tool behavior: Claude Code tools.