Claude Code Permissions and Security: The Complete Setup Guide (2026)
How Claude Code's permission system actually works — permission modes, settings.json, allowlists, hooks, and sandboxing — plus a config you can copy for a safer, faster setup.
Claude Code Permissions and Security: The Complete Setup Guide (2026)
The single most common Claude Code complaint isn't quality — it's the permission prompt. Every git push, every rm, every unfamiliar Bash command stops the agent and asks "yes or no." Click through enough of these on autopilot and you'll eventually approve something you shouldn't have. Turn the prompts off entirely and you've handed an autonomous agent a shell with no guardrails.
Neither extreme is the right answer. Claude Code's permission system is configurable at a granularity most people never touch, and getting it right is the difference between an agent you trust with real work and one you babysit line by line. This guide covers how the system actually works and gives you a config you can adapt today.
How Claude Code's Permission Model Works
Every tool call Claude Code makes — reading a file, running a shell command, editing code, calling an MCP tool — passes through a permission check before it executes. That check resolves one of three ways:
This resolves per permission mode, which sets the default posture for anything not explicitly ruled on:
| Mode | Default behavior | Use for |
|---|---|---|
| Default | Prompts for anything not explicitly allowed | Day-to-day work on a codebase you know |
| Plan | Read-only; no edits or execution until you approve a plan | Exploring an unfamiliar codebase, reviewing before acting |
| Accept edits | Auto-allows file edits, still prompts for Bash/destructive actions | Fast iterative coding on non-critical branches |
| Bypass permissions | Skips prompts entirely | Sandboxed/CI environments only — never your primary machine |
The mode is the coarse dial. The allowlist and denylist rules in settings.json are the fine one, and that's where most of the real security work happens.
Where Permissions Live: The settings.json Hierarchy
Claude Code reads permission settings from three layers, applied in order of increasing specificity:
~/.claude/settings.json # user-level, applies to every project
<project>/.claude/settings.json # project-level, checked into git, team-shared
<project>/.claude/settings.local.json # personal overrides, gitignoredProject-level settings are how a team keeps everyone on the same security posture — commit .claude/settings.json and every contributor inherits the same allow/deny rules the moment they clone the repo. Personal overrides in settings.local.json let an individual loosen or tighten things without touching the shared config.
Writing Allow and Deny Rules
Rules live under the permissions key and match on tool name plus, for Bash, a command pattern:
json{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff*)",
"Bash(npm test*)",
"Bash(npm run lint*)",
"Read(**)"
],
"deny": [
"Bash(git push --force*)",
"Bash(rm -rf*)",
"Bash(git reset --hard*)",
"Bash(curl* | sh*)"
]
}
}A few rules that matter more than they look:
- Deny rules win. If an action matches both an allow and a deny pattern, it's denied. This lets you set broad allows (
Bash(git*)) and then carve out specific exceptions for the genuinely dangerous subcommands, rather than trying to enumerate every safe git command individually. - Wildcards match prefixes, not intent.
Bash(npm run*)allows any npm script — including one nameddeploy-prodif yourpackage.jsonhas one. Audit what your allowed prefixes actually expand to, not just what you meant by them. - Read is usually safe to broadly allow. File reads, greps, and directory listings rarely need per-path scrutiny. Save prompts for things that mutate state: writes, shell execution, network calls, and git history rewrites.
The Four Categories of Actions Worth Thinking About Separately
1. File edits. Generally low-risk and reversible — Claude Code's own edit tool requires the file to have been read first, and changes are visible in your diff before you commit anything. Safe to broadly allow in most projects; the real check happens at code review, not at edit time. 2. Shell commands. The highest-variance category.ls, git status, and npm test are safe to blanket-allow. Anything destructive (rm, git push --force, git reset --hard), anything that touches credentials (cat .env, aws configure), and anything that pipes remote content into a shell (curl | sh) belongs on an explicit deny list or behind a prompt every time.
3. MCP tool calls. Each connected MCP server can expose tools with real-world side effects — sending a Slack message, creating a GitHub PR, writing to a database. Treat a newly-connected MCP server the way you'd treat a new npm dependency: check what it can actually do before granting it a broad allow rule. See our guide on choosing MCP servers for what to check before installing one.
4. Network-adjacent actions. WebFetch, WebSearch, and any tool that pulls in external content should be treated as reading untrusted input, not trusted instructions. A scraped web page or a fetched API response can contain text engineered to look like a command — this is the core mechanism behind prompt-injection attacks. Claude Code's default behavior of not auto-executing on content it just fetched is a safety feature; don't build automation around it that removes that pause.
Hooks: Enforcing Rules the Model Can't Talk Its Way Around
Permission rules govern what Claude Code asks to do. Hooks run outside the model entirely — they're shell commands your settings.json wires up to fire on specific events (before a tool call, after a tool call, on session start), and they execute unconditionally, regardless of what the model "decides."
json{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "./scripts/audit-log.sh" }]
}
]
}
}This is the right layer for anything that must happen every time, no exceptions: logging every shell command to an audit trail, blocking commits that don't pass a secret scanner, or enforcing that tests run before any file outside src/ gets touched. Because hooks run as plain shell commands independent of the model's judgment, they're not susceptible to the model being talked into skipping a step — which makes them the correct tool for hard requirements, not permission rules alone.
Sandboxing: The Other Half of the Story
Permission rules control what Claude Code asks permission for. They don't change what's possible if something goes wrong inside an approved action — a bypass permissions session run inside a container that only has access to a scratch directory is safer than a default mode session run directly against your primary machine with production credentials in the environment.
For genuinely autonomous runs — overnight agent sessions, CI-triggered fixes, anything running unattended — pair a permissive mode with an isolated environment: a fresh git worktree, a container with no access to your real credentials, and a scoped-down cloud environment rather than your laptop. The permission system reduces unwanted actions; sandboxing limits the blast radius if one happens anyway. Use both, especially as autonomy increases.
A Config to Start From
For a typical team codebase, this is a reasonable default balance of speed and safety:
json{
"permissions": {
"allow": [
"Read(**)",
"Bash(git status)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(npm test*)",
"Bash(npm run lint*)",
"Bash(npm run build*)"
],
"deny": [
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(rm -rf*)",
"Bash(*curl* | sh*)",
"Bash(cat .env*)",
"Bash(cat *.pem)",
"Bash(cat *credentials*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "./scripts/log-bash.sh" }]
}
]
}
}Commit this at the project level, let individual contributors extend it in settings.local.json for their own workflow, and revisit the deny list any time a new class of risky command shows up in your stack.
Common Mistakes
Turning on bypass mode "just for this session" on a real machine. It's rarely just for this session — the setting persists, and the next session inherits it. Reserve bypass mode for genuinely sandboxed environments. Allowlisting by habit instead of by risk. Approving the same prompt five times in a row and then adding a blanket allow rule feels efficient, but check what the wildcard actually covers before committing it —Bash(git*) is a much bigger grant than Bash(git status).
Treating fetched content as trusted. If a workflow pipes a scraped web page or an API response into a decision Claude Code acts on, that content can carry injected instructions. Keep a human review step between untrusted external content and any destructive action.
No audit trail. Without a PreToolUse logging hook, you have no record of what an autonomous session actually did if something goes wrong. Fifteen minutes of hook setup buys you a real answer during an incident instead of a guess.
Frequently Asked Questions
Does denying a Bash pattern block it everywhere, even inside a subagent? Yes — permission rules apply to the whole session, including subagents spawned via the Agent tool. A subagent inherits the parent session's permission settings; it doesn't get a fresh, unrestricted slate. Can I have different rules for different branches or directories? Not natively per-branch, but you can scope rules per-project by keeping separate.claude/settings.json files if you work across multiple repos with different risk profiles — a production infra repo and a personal prototype repo shouldn't share a permission config.
What happens if a hook command fails? A failing PreToolUse hook blocks the tool call it's guarding — this is exactly why hooks are the right layer for hard requirements like "never commit without running the linter." Design hook scripts to fail loudly and specifically, not silently, so you can tell a real block from a broken script.
Is settings.local.json visible to teammates? No — it's meant to be gitignored, which is precisely why it's the right place for personal preferences (looser rules on your own scratch branches, an extra allow for a tool only you use) that shouldn't become the team default.
Key Takeaways
- Permission modes set the default posture; allow/deny rules in
settings.jsonhandle the fine-grained cases — use both together, not one instead of the other. - Deny rules always win over allow rules, so build broad allows with explicit deny carve-outs for the genuinely dangerous commands.
- Hooks enforce requirements outside the model's judgment — use them for anything that must happen every time, no exceptions.
- Permissions reduce unwanted actions; sandboxing limits the damage if one slips through anyway. Autonomous or unattended runs need both.
- This is directly testable material — agent security and permission design shows up in the Agentic Architecture domain of the Claude Certified Architect exam.
Next Steps
Security and permission design is one of the more commonly under-prepared areas on the CCA-F exam. AI for Anything's practice test bank includes scenario-based questions on exactly this — permission scoping, hook design, and sandboxing tradeoffs — with full explanations for each answer. If you're still assembling your MCP server stack, pair this with our guide to choosing the right MCP servers before you start writing allow rules for them.
Rohit Mote
Founder, AI for Anything
Rohit Mote is the founder of AI for Anything and builds AI-powered products full-time across the Infinite Products Machine portfolio. Every guide is grounded in hands-on daily use of Claude, Claude Code, and the broader AI tool ecosystem in production systems.
How we create and review our guides →