Permissions govern every action Claude Code takes on your machine, from reading a file to executing a shell command. Understanding the six modes, the rule syntax in settings.json, and how hooks gate tool calls lets you work faster without sacrificing safety. This guide covers the full interactive permission system, from manual approval to enterprise lockdown, with copy-ready examples.

Permissions: What You’ll Learn
Permissions are the trust boundary between Claude Code and your filesystem, shell, and network. Without them, an AI agent with write access could delete critical files, leak secrets, or run destructive commands. This guide covers the interactive permission system in full, so you can configure it correctly for solo projects, team repos, and enterprise environments.
You will learn the six permission modes, how allow/ask/deny rules work, glob pattern matching, the five-layer settings hierarchy, managed enterprise lockdown, hook-based gating, and sandboxing. By the end, you will have a mental model for choosing the right mode for each situation and writing durable rules that survive across sessions.
What Are Claude Code Permissions?
Claude Code is an AI agent that can read files, write code, run shell commands, and call external tools through the Model Context Protocol. Every one of those actions passes through the permission system before execution. The system asks a simple question for each tool call: should this run automatically, should it require human approval, or should it be blocked entirely?
The answer depends on three things working together: the active permission mode (how aggressive the agent is), the rule set in your settings files (what is explicitly allowed or denied), and any hooks that run before the tool call to add custom logic. Together, these layers give you fine-grained control over what the agent can do on your behalf.
If you have used sudo on Linux, the mental model is similar: a tool wants to do something privileged, and the system checks rules before allowing it. The difference is that Claude Code’s permission system is far more granular. You can allow a specific bash command pattern, deny reads of a particular file, or force a prompt for anything touching your git history, all without leaving your editor.
The permission system is not a single on-off switch. It is a layered evaluation pipeline. First, PreToolUse hooks run and can short-circuit the decision. Then the permission mode sets the baseline behaviour. Finally, explicit allow, ask, and deny rules refine that baseline for specific tools and parameters. Understanding this pipeline is the key to configuring permissions confidently.
For a broader look at how permissions fit into the agent’s overall behaviour, see the official permissions reference and our Claude Code project setup guide for practical file-structure advice.
The Six Permission Modes Explained
Claude Code ships with six permission modes, each defining a different baseline for what runs without asking. Choosing the right mode is the single most impactful permissions decision you will make. Let us walk through each one in detail.
1. default (Manual)
The default mode is the most conservative interactive mode. Claude Code can read files freely, but anything that modifies state, writing files, editing code, running commands, requires explicit human approval. In the CLI, this mode is labelled Manual as of version 2.1.200, though the config value remains default. The CLI also accepts manual as an alias.
This is the mode you should start with if you are new to Claude Code, working on a sensitive repository, or doing anything where you want to review every change before it happens. It is the safest mode that still lets the agent be productive, because reads are free but writes pause for your input. Teams still tightening their permissions often stay here the longest.
2. acceptEdits
The acceptEdits mode is designed for rapid iteration. In addition to reads, it automatically approves file edits and a curated set of filesystem commands: mkdir, touch, mv, cp, rm, rmdir, and sed. You no longer need to approve every small file change, which dramatically speeds up coding sessions.
However, acceptEdits does not auto-approve arbitrary bash commands. Running a build script, a test suite, or a package manager still prompts you. This makes it a sweet spot for development: file changes are fast, but anything that could have side effects beyond the working directory still gets reviewed.
3. plan
The plan mode is read-only. Claude Code can read files and explore the codebase, but it cannot make changes. Instead, it produces a structured plan of what it would do. This mode is invaluable when you want the agent to analyse a large codebase, propose a refactoring strategy, or investigate a bug before you commit to any changes.
Plan mode respects certain protected paths even more strictly. It will not touch .git, .claude, .vscode, .idea, .husky, .cargo, .devcontainer, .yarn, or .mvn directories unless you are in bypassPermissions mode. Use plan mode when exploring unfamiliar code or evaluating a proposed approach.
4. auto
The auto mode is the most permissive interactive mode. It allows everything, reads, writes, edits, and bash commands, but runs each action through a background classifier that checks for safety risks. If the classifier detects something potentially dangerous, it still prompts you. This mode is labelled Auto in the CLI and is currently a research preview.
Auto mode is ideal for long, multi-step tasks where you trust the agent but want a safety net without loosening your permissions carelessly. For example, asking Claude Code to refactor ten files across a project: in manual mode you would approve forty individual tool calls, while in auto mode the classifier handles the routine ones and only flags genuinely risky operations.
5. dontAsk
The dontAsk mode is the lockstep option. It only runs tools that are explicitly pre-approved in your allow rules. Everything else is auto-denied, not auto-approved. This mode has no entry in the Shift+Tab cycle; you set it via CLI flag or settings.json.
This mode is designed for CI pipelines, automated scripts, and headless workflows where a prompt would hang forever. If the agent tries to use a tool you have not pre-approved, it gets a denial and must work around it. This makes dontAsk deterministic: the agent can only do exactly what you have whitelisted, nothing more. For the full headless mode comparison, see our Claude Code headless mode guide, which covers all six headless permission flags in depth.
6. bypassPermissions
The bypassPermissions mode disables the permission system for nearly everything. Reads, writes, edits, bash commands, filesystem operations, and MCP tool calls all run without asking. Two documented exceptions remain: explicit ask rules still force a prompt, and destructive commands like rm -rf / or rm -rf ~ are blocked as a hardcoded circuit breaker. This mode is labelled Bypass permissions in the CLI.
This mode exists for one scenario: isolated containers and virtual machines where a mistake has no real-world consequences. A throwaway Docker container, a fresh VM, a CI runner that is destroyed after the job. Treat these permissions as sacred: never use bypassPermissions on a machine with internet access, cloud credentials, SSH keys, or database connection strings. It provides zero protection against prompt injection attacks.
Administrators can block this mode in managed settings using permissions.disableBypassPermissionsMode: "disable", and they can block the auto mode with permissions.disableAutoMode: "disable". This is how enterprises prevent dangerous modes from being used on corporate machines.
Switching Modes: Flags, Shift+Tab, and settings.json
There are three ways to set the active permission mode, each suited to different workflows. Understanding when to use each one helps you avoid repeating configuration every session, and it keeps your permissions predictable across projects.
CLI Flag (Session-Level)
Pass the mode when launching Claude Code. This applies for the entire session and overrides the default from settings.json:
# Launch in acceptEdits mode for fast iteration
claude --permission-mode acceptEdits
# Launch in plan mode for read-only exploration
claude --permission-mode plan
# Launch in dontAsk mode for a CI script
claude --permission-mode dontAsk --print "run the test suite"
Shift+Tab During a Session
Press Shift+Tab to cycle through modes while Claude Code is running. The cycle is default to acceptEdits to plan, with bypassPermissions and auto appearing in the cycle only if they are enabled. This is the fastest way to switch modes mid-task without restarting the session.
Persistent in settings.json
For a default that applies every time you start Claude Code in a project, set it in settings.json. This is the recommended approach for team repositories where everyone should share the same permissions baseline:
{
"permissions": {
"defaultMode": "acceptEdits"
}
}
You can place this in the project-level .claude/settings.json (committed to git) so the whole team gets it, or in ~/.claude/settings.json for a personal default across all projects.
The /permissions Command
The /permissions slash command opens an interactive UI that lists every active permission rule and shows which settings.json file each rule came from. This is your dashboard for understanding exactly what Claude Code can and cannot do right now. Reviewing your permissions here regularly catches drift before it becomes a problem.
When you run /permissions, you see three categories of rules. Allow rules let Claude use a tool without asking, such as Bash(npm run *). Ask rules force a prompt every time, such as Bash(git push *). Deny rules block a tool entirely, such as Bash(curl *).
You can add and remove rules directly from this UI, or edit the underlying settings.json files manually for more control. The UI is especially helpful when debugging why a particular tool call was blocked or approved, because it shows the full rule set and its source at a glance.
The evaluation order is critical to understand. Rules are checked in this sequence: deny first, then ask, then allow. The first match wins, and specificity does not change the order. If you have Bash(curl *) in deny and Bash(curl https://api.example.com/*) in allow, the deny rule wins because deny is always evaluated first. There is no specificity override.
Rule Syntax: Allow, Ask, and Deny
Permission rules live under the permissions key in settings.json. Each of the three arrays, allow, ask, and deny, takes rule strings in a specific format. Let us break down the syntax with practical examples.
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)",
"Bash(git status)",
"Bash(git diff *)",
"Read(~/.zshrc)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf /*)",
"Read(./.env)",
"Read(**/.env)",
"mcp__*"
],
"ask": [
"Bash(git push *)",
"Bash(docker *)"
]
}
}
The general format is ToolName(pattern). The tool name identifies which tool the rule applies to. The pattern inside parentheses uses glob matching against the tool’s parameters. For bash commands, the pattern matches the command string itself. Mastering this syntax is the fastest way to write precise permissions.
Allow Rules in Practice
Allow rules are the backbone of a productive workflow. Common patterns include allowing your build and test commands, git status and diff for safe reads, and file reads of your shell configuration. Each allow rule removes one prompt from your workflow, which compounds into significant time savings over a session. Well-tuned permissions keep things fast without sacrificing safety.
Deny Rules as Guardrails
Deny rules are your safety net. They block tools that should never run without explicit human oversight. A well-configured deny list prevents network egress via curl and wget, protects secret files like .env, and can disable entire tool categories like MCP servers with the mcp__* wildcard. These permissions form the backbone of a defensible security posture.
Ask Rules for High-Stakes Actions
Ask rules force a prompt every single time, even in aggressive modes like acceptEdits. Use them for actions that are sometimes safe but always important: git push, docker commands, anything that publishes or deploys. The prompt ensures you stay in the loop for irreversible operations. Getting these permissions right protects you from mistakes you cannot undo.
Glob Patterns and Matching Rules
The pattern syntax inside rule parentheses follows glob conventions, but there are important nuances specific to Claude Code’s permission system. Getting these details right prevents both false approvals and false denials.
The Space Matters: Word Boundaries
A space in the pattern acts as a word boundary separator. Bash(ls *) matches ls -la and ls /tmp, but it does not match lsof because there is no space after ls. This prevents overly broad matches from catching commands that merely start with the same characters. Precise patterns like this stop your permissions from being broader than intended.
# "Bash(ls *)" matches:
ls -la # match (space after ls)
ls /tmp # match (space after ls)
# "Bash(ls *)" does NOT match:
lsof # no match: no space, different command
ls # no match: no space + no args (use "Bash(ls)" or "Bash(ls:*)")
No Space: Prefix Matching
Without a space, the pattern matches by prefix. Bash(ls*) matches both ls -la and lsof, because it matches anything starting with the literal string ls. This is usually too broad for safety, so prefer the space-separated form.
Colon Syntax Equivalence
The colon form Bash(ls:*) is equivalent to Bash(ls *). Both match ls followed by arguments. Use whichever reads more clearly in your config; they behave identically.
Bare Tool Name
Bash(*) is equivalent to Bash with no parentheses. As a deny rule, this removes the Bash tool entirely. As an allow rule, it approves every bash command. Neither is recommended except in very specific scenarios.
Compound Commands
When a command uses separators like &&, ||, ;, |, or &, each subcommand is checked independently against the rules. For example, npm test && npm run deploy checks npm test and npm run deploy separately. If npm run deploy hits an ask rule, the whole compound command prompts, even if npm test is allowed.
Wrapper Stripping
Claude Code strips common wrapper commands before evaluating the rule. Wrappers like timeout, time, nice, nohup, and stdbuf are removed, so timeout 30 npm test is evaluated as npm test. This means your rules can target the actual command without listing every possible wrapper variant.
Tool-Name Globs
The wildcard mcp__* matches every MCP tool, regardless of server name or tool name. This is the quickest way to deny or allow all external tools at once. You can also be more specific: mcp__github__* targets all tools from the GitHub MCP server. This wildcard reshapes your MCP permissions in one line.
Parameter Matching
Rules can match specific parameter values, not just tool names. This is powerful for nuanced access control:
{
"permissions": {
"deny": [
"Agent(model:opus)",
"Agent(isolation:worktree)"
],
"ask": [
"Bash(run_in_background:true)"
]
}
}
With Agent(model:opus) in deny, the agent cannot spawn subagents using the Opus model. With Bash(run_in_background:true) in ask, any backgrounded bash command prompts you first. This level of granularity lets you enforce policies like “no expensive model usage” or “no unattended background processes” across an entire organisation.
The Settings Hierarchy
Claude Code loads settings from five sources, each overriding the one below it. Understanding this precedence order is essential for predicting which rules win when multiple files define conflicting permissions, especially in larger teams with layered permissions files.
1. Managed Settings (Highest Precedence)
Server-managed settings, MDM-deployed configuration, and the file at /etc/claude-code/managed-settings.json sit at the top. These cannot be overridden by any user or project setting. This is where enterprises enforce mandatory security policies that no developer can disable. These enterprise-grade permissions cannot be loosened by any individual machine.
2. Command Line Flags
CLI flags like --permission-mode apply for the current session only. They override settings.json files but are superseded by managed settings. This layer is useful for one-off tasks where you need a different mode temporarily.
3. Local Project Settings
The file .claude/settings.local.json in your project directory holds personal overrides. This file is typically gitignored, so it does not get committed. Use it for machine-specific paths, personal allow rules, or experiments you do not want to share with the team. Keep any experimental permissions here until you are ready to promote them.
4. Project Settings
The file .claude/settings.json in your project directory is committed to version control. This is where team-wide permission rules live. Every developer who clones the repo inherits these rules, ensuring consistent behaviour across the team. Reviewing these permissions during code review keeps everyone aligned.
5. User Settings (Lowest Precedence)
The file ~/.claude/settings.json in your home directory holds personal defaults that apply to every project. This is where you set your preferred default mode and your personal allow or deny rules that should follow you everywhere. These personal permissions travel with you across every repository.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
],
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/credentials.json)",
"Read(**/id_rsa)"
]
}
}
The $schema line at the top enables VS Code autocomplete for the entire settings file. It is optional but highly recommended, as it catches typos in key names and suggests valid values as you type.
Enterprise Lockdown: Managed Settings
For organisations that need strict control over what Claude Code can do, the managed settings layer provides several powerful options for locking down permissions. These settings are enforced server-side or via MDM and cannot be overridden by users or projects.
Locking Down Permission Rules
Setting allowManagedPermissionRulesOnly to true prevents users and projects from defining their own allow, ask, or deny rules. Only the rules in the managed settings file are active. This ensures that the security team’s deny list cannot be circumvented by a permissive project-level config. Locking down permissions this way removes an entire class of misconfiguration risk.
Locking Down Hooks
Setting allowManagedHooksOnly to true ensures that only managed hooks and force-enabled plugin hooks can run. User-defined hooks in settings.json or settings.local.json are ignored. This prevents a developer from accidentally or maliciously disabling a security hook. It keeps the permissions enforcement layer intact even under a compromised local environment.
Locking Down MCP Servers
Setting allowManagedMcpServersOnly to true restricts MCP server usage to only those approved by administrators. This is critical for preventing data exfiltration through unvetted external tool servers that might send sensitive data to third-party APIs. It closes an easy path around your other permissions.
Restricting Login to One Organisation
Setting forceLoginOrgUUID to your organisation’s UUID ensures that Claude Code can only authenticate against that specific organisation. This prevents users from connecting personal accounts that might carry different, looser permissions.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"disableBypassPermissionsMode": "disable",
"disableAutoMode": "disable",
"allowManagedPermissionRulesOnly": true
},
"hooks": {
"allowManagedHooksOnly": true
},
"mcpServers": {
"allowManagedMcpServersOnly": true
},
"forceLoginOrgUUID": "your-org-uuid-here"
}
With this configuration deployed via MDM, no developer on the machine can bypass permissions, enable auto mode, define custom rules, run custom hooks, connect to unapproved MCP servers, or log in outside the organisation. This is the full enterprise lockdown profile.
Hook-Based Permission Gating
Hooks are the most powerful and flexible layer of the permission system. A PreToolUse hook runs before the permission prompt and can make its own decision about whether a tool call should proceed. This lets you implement custom logic that goes far beyond glob patterns. Hooks let you encode permissions logic that no static rule list could express.
A PreToolUse hook can return one of three decisions. Deny blocks the tool call entirely, even in bypassPermissions mode. Ask forces a permission prompt regardless of the mode or allow rules. Allow approves the call and skips the prompt entirely.
How Hooks Interact with Rules
The interaction between hooks and rules has a specific precedence. A hook deny always wins, overriding even an allow rule. However, a matching deny rule in settings.json also wins regardless of what the hook says. In practice, this means deny rules are the ultimate backstop, and hooks add dynamic logic on top.
For example, if your deny list blocks Bash(curl *) and a PreToolUse hook tries to allow it, the deny rule wins. Conversely, if your allow list permits Bash(npm run *) and a PreToolUse hook returns deny, the hook wins. This design ensures that static security rules cannot be weakened by hook bugs.
Hook Handler Types
Claude Code supports five types of hook handlers. Command hooks run a shell command and check its exit code (exit 2 means deny). HTTP hooks send a POST request to a URL and parse the response. MCP tool hooks call a specific MCP tool. Prompt hooks use an LLM to evaluate the call. Agent hooks are experimental and spawn a subagent for evaluation.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/usr/local/bin/check-bash-safety.sh"
}
]
}
]
}
}
In this example, every Bash tool call triggers the script check-bash-safety.sh. If the script exits with code 2, the call is denied. If it exits 0, the call proceeds normally. This lets you enforce arbitrary custom checks, such as scanning command strings for dangerous patterns, logging all commands for audit, or integrating with an external approval system.
Beyond PreToolUse: The Full Hook Event Catalogue
PreToolUse is just one of over thirty hook events. Others include PermissionRequest (fires when the permission prompt would appear), PermissionDenied (fires after a denial), PostToolUse (fires after a tool completes), SessionStart, UserPromptSubmit, Stop, SubagentStart and SubagentStop, and WorktreeCreate and WorktreeRemove.
For the complete hook reference, including handler configuration and event payloads, see our dedicated Claude Code Hooks Guide. Hooks and permissions are deeply intertwined, and understanding both is essential for building a secure agent workflow.
Sandboxing and OS-Level Restrictions
Beyond the permission rules and hooks, Claude Code supports OS-level sandboxing for bash commands. Sandboxing adds filesystem and network restrictions enforced by the operating system, not by the permission system. This is a defence-in-depth measure that catches what rules might miss.
When sandboxing is enabled, bash commands run in a restricted environment. The sandbox can limit which directories are readable and writable, and it can block network access entirely. Even if a command somehow bypasses the permission prompt, the OS-level sandbox prevents it from accessing files or network resources outside the allowed scope.
Sandboxing is particularly valuable in auto and acceptEdits modes, where commands run with less human oversight. Combined with a well-configured deny list and PreToolUse hooks, sandboxing creates multiple independent layers of protection. If one layer fails, the others catch the problem. The official sandboxing documentation at /en/sandboxing on code.claude.com covers the full configuration options.
Security Best Practices
Configuring permissions correctly is not just about convenience. It is about protecting your data, your credentials, and your infrastructure. Here are the most important security principles, drawn from the official documentation and real-world experience.
Prefer auto Over bypassPermissions
The bypassPermissions mode offers zero protection against prompt injection. A malicious instruction hidden in a file, a web page, or a command output can trick the agent into running harmful commands, and bypass mode will not stop it. The auto mode, by contrast, runs every action through a safety classifier that can catch suspicious patterns. Use auto for long tasks; reserve bypassPermissions for disposable containers.
Never Bypass on Real Machines
If your machine has internet access, cloud credentials (AWS, GCP, Azure), SSH keys, or database connection strings, never use bypassPermissions. A single prompt injection in a dependency’s README or a log file could exfiltrate your credentials, delete your infrastructure, or open a backdoor. The risk is not theoretical; it has happened.
Commit Your settings.json
A committed .claude/settings.json with persistent deny rules is more reliable than remembering CLI flags. Every team member who clones the repo inherits the same protection. Review the file in code reviews just like any other security-relevant config. Treat your deny list as infrastructure, not preference.
Use Hooks for Mandatory Checks
When a check must run on every single tool call, use a PreToolUse hook, not a rule. Rules match patterns; hooks run code. If you need to log every command, scan for secrets, or integrate with an external approval workflow, a hook is the right tool. Because hook denies override allow rules, they are the ultimate enforcement mechanism.
Layer Your Defences
No single mechanism is sufficient. Combine a conservative default mode, a thorough deny list, PreToolUse hooks for custom logic, and OS-level sandboxing for filesystem and network isolation. Each layer catches different classes of threats, and together they make it extraordinarily difficult for an accidental or injected command to cause real damage. For more on extending Claude Code safely, see our Claude Code Extensibility Guide.
A Worked Example
Let us walk through a realistic scenario to see how all these pieces fit together. Imagine you are setting up Claude Code for a team of five developers working on a Node.js web application. The repo lives on GitHub, deploys to AWS, and has a .env file with database credentials that must never be exposed.
You start by choosing the default mode. The team needs speed but also safety, so acceptEdits is the baseline. Developers can iterate on code without approving every file change, but bash commands still prompt. You set this in the committed .claude/settings.json so everyone gets it automatically.
Next, you define the allow list. The team’s common commands, building, testing, linting, and safe git reads, go in allow so they never interrupt flow. You add Bash(npm run *) to cover build, test, and lint scripts. You add Bash(git status), Bash(git diff *), and Bash(git log *) for safe repository inspection.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(npm run *)",
"Bash(npm test *)",
"Bash(npx eslint *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)",
"Bash(docker *)",
"Bash(serverless deploy *)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf /)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/credentials.json)",
"Read(**/*.pem)",
"Read(**/id_rsa)"
]
}
}
The ask list catches deployment and publishing actions. git push, npm publish, docker commands, and serverless deploy all force a prompt, even though the mode is acceptEdits. This ensures that irreversible operations always pass through human review, while routine development remains fast.
The deny list is your backstop. You block curl and wget to prevent network egress that could exfiltrate data. You block reads of .env files, credential files, SSH keys, and PEM certificates. The ** glob matches at any depth, so these rules catch secrets anywhere in the project tree, not just the root.
Now you add a PreToolUse hook for audit logging. Every bash command gets written to a log file with a timestamp, so you can review what the agent did during each session. The hook exits 0, so it never blocks, but it records everything for forensic purposes.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo "$(date -Iseconds) $CLAUDE_TOOL_INPUT" >> /var/log/claude-audit.log"
}
]
}
]
}
}
Finally, you enable sandboxing for filesystem and network restrictions. Even if a command slips past the deny list and the hook, the OS-level sandbox prevents it from reaching the network or writing outside the project directory. With all four layers in place, default mode, rules, hooks, and sandboxing, the team can work confidently knowing that multiple independent systems guard against mistakes and attacks.
This configuration is not hypothetical. It reflects a pattern that production teams use every day. The key insight is that no single layer is trusted completely. Each layer assumes the others might fail, and together they create a security posture that is far stronger than any individual mechanism could provide alone.
Permissions: Common Mistakes to Avoid
Even experienced developers trip over the same issues with permissions. These mistakes range from mild inconveniences to serious security vulnerabilities. Avoiding them saves time and prevents dangerous misconfigurations.
- Using bypassPermissions on a real machine. This is the most dangerous mistake. If your machine has internet access, cloud credentials, or SSH keys, bypass mode gives a prompt-injected command full access to everything. Always use a throwaway container or VM, and prefer
automode for long tasks on real machines. - Forgetting that deny always beats allow. Developers sometimes add a specific allow rule expecting it to override a broad deny rule. It will not. Evaluation order is deny, then ask, then allow, and the first match wins regardless of specificity. If you need to allow something that a deny rule blocks, narrow the deny rule instead.
- Not committing settings.json to the repo. If permission rules live only in
settings.local.jsonor a developer’s memory, new team members start without protection. Commit the project-level settings.json so every clone inherits the same security baseline. Review it in code reviews like any other config. - Confusing
ls *withls*in patterns. The space is a word boundary.Bash(ls *)matchesls -labut notlsof.Bash(ls*)matches both, which is usually too broad. Always include the space unless you specifically want prefix matching, and test your patterns with the/permissionscommand.
Permissions: Best Practices
- Start with
defaultmode and add allow rules incrementally. Each new rule removes a prompt, so add them only when a command is genuinely safe and frequent. This builds a tight, intentional allow list rather than a permissive one that grew accidentally. - Protect every secrets file with deny rules. Use
Read(**/.env),Read(**/.env.*),Read(**/credentials.json),Read(**/*.pem), andRead(**/id_rsa)to block reads at any depth. Secrets live in surprising places, and the**glob catches them all. - Use ask rules for irreversible actions. Anything that publishes, deploys, pushes to remote, or modifies shared infrastructure should force a prompt every time, regardless of the active mode. The cost of one extra click is trivial compared to the cost of an accidental deploy.
- Layer PreToolUse hooks for mandatory logic. When a check must run every time, such as audit logging, secret scanning, or external approval, a hook is more reliable than a rule. Hook denies override allow rules, making them the strongest enforcement mechanism in the system.
- Review and prune your rules regularly. Permission configs accumulate cruft over time. Stale allow rules for commands you no longer use create unnecessary exposure. Schedule a quarterly review of your settings.json and remove anything that does not reflect your current workflow.
Permissions: Frequently Asked Questions
What is the difference between default and acceptEdits mode?
default (labelled Manual) requires approval for all writes and commands, allowing only reads freely. acceptEdits auto-approves file edits and filesystem commands like mkdir or rm, but still prompts for arbitrary bash. Use default for sensitive work, acceptEdits for fast iteration.
Can a deny rule be overridden by an allow rule?
No. Evaluation order is always deny, then ask, then allow — the first match wins regardless of specificity. If Bash(curl *) is in deny, no allow rule overrides it. Narrow or remove the deny rule to permit it instead.
Is bypassPermissions safe if I trust the agent?
No. Bypass mode provides zero protection against prompt injection, where malicious instructions hidden in files or output trick the agent into running harmful commands. Trust in the agent doesn’t matter; the threat comes from untrusted data it processes.
How do I share permission rules with my team?
Commit .claude/settings.json to your repository. Every developer who clones the repo inherits the same allow, ask, and deny rules. Keep personal overrides in .claude/settings.local.json, which is gitignored. Review the committed file in code reviews like any other security configuration.
What does the mcp__* deny rule do?
The mcp__* pattern matches every MCP tool, regardless of server. Adding it to deny blocks all external MCP tool calls entirely — the fastest way to stop the agent calling third-party APIs. Target specific servers instead with patterns like mcp__github__*.
Permissions in Claude Code form a layered defence system: six modes set the baseline, allow/ask/deny rules refine it, hooks add dynamic logic, and sandboxing enforces OS-level boundaries. Start conservative, add allow rules intentionally, protect secrets with deny rules, force prompts for irreversible actions, and never use bypass mode on real machines. Commit your settings.json, layer your defences, and review regularly.