Claude Code Permissions Security Configuration Enterprise developer tools

Claude Code Permission Modes and Rule Syntax: Complete Guide (2026)

The Prompt Shelf ·

Claude Code’s permission system is one of its most practical features, and also one of its most misunderstood. Many developers default to accepting prompts one by one, never configuring the rules layer. That works for a solo developer on a personal project. For a team, for CI/CD, or for any environment where you care about what Claude can do without asking, the permission system is worth understanding in full.

This guide covers all six permission modes, the complete rule syntax, and the managed settings layer for enterprise deployments.


The Permission System in 60 Seconds

Claude Code runs a tiered permission check before every tool use:

Tool typeDefault behaviorSticky approval
Read-only (file reads, Grep, Glob)No promptN/A
Bash commandsPrompt on first usePermanent per project + command
File modification (Edit, Write)Prompt on first useUntil session end

The evaluation order is always: deny → ask → allow. The first matching rule wins. A deny rule at any scope beats an allow rule at any scope.


The Six Permission Modes

Set the active mode in your settings file:

{
  "defaultMode": "acceptEdits"
}

Or pass it as a CLI flag for a one-off session:

claude --permission-mode acceptEdits

1. default — Standard Interactive

The baseline. Claude prompts for permission on first use of each tool in each project. Approvals for Bash commands are permanent per project directory and command. File edit approvals last until session end.

Best for: interactive development sessions where you want oversight on new tool uses.

2. plan — Read-Only Exploration

Claude Code’s Plan Mode. Claude can read files and run read-only shell commands to explore your codebase, understand the task, and build a plan. It cannot edit source files.

claude --plan

What’s allowed in plan mode:

  • Reading any file
  • Running read-only Bash commands (ls, cat, git log, grep, etc.)
  • Browsing the web (if WebFetch is permitted)
  • Creating and reviewing plans

What’s blocked:

  • Writing or editing files
  • Running Bash commands with side effects
  • Executing any command that modifies state

Best for: code review, architectural analysis, getting a detailed plan before execution, situations where you want Claude’s analysis before committing to changes.

3. acceptEdits — Auto-Approve File Changes

Auto-approves file edits and a common set of filesystem commands (mkdir, touch, mv, cp, etc.) for paths within the working directory and any additionalDirectories. Bash commands that aren’t in the pre-approved list still prompt.

{
  "defaultMode": "acceptEdits"
}

Best for: active development sessions where you’re working on files you own. You get Claude’s full editing capability without per-file prompts while still seeing prompts for Bash commands that could have broader effects.

4. auto — Background Safety Checks

Auto-approves tool calls using background safety checks that verify each action aligns with your original request. Still a research preview as of mid-2026.

The safety classifier examines whether a proposed tool use is coherent with what you asked for. A request to “add error handling to the auth module” that triggers an attempt to push to production would be caught and rejected.

{
  "defaultMode": "auto"
}

Administrators can disable auto mode organization-wide:

{
  "permissions": {
    "disableAutoMode": "disable"
  }
}

Best for: developers who want maximum velocity with a safety net. Understand that the safety classifier is not infallible.

5. dontAsk — Explicit Allowlist Only

The inverse of auto. Everything is denied unless explicitly pre-approved via /permissions or permissions.allow rules. Claude gets no tools it hasn’t been granted.

{
  "defaultMode": "dontAsk"
}

Best for: creating tightly scoped agents, embedding Claude Code in applications where you want precise tool control, or security-sensitive environments where you need to know exactly what Claude can touch.

6. bypassPermissions — No Prompts

Skips all permission prompts. Hard circuit breakers remain: rm -rf / and rm -rf ~ still prompt.

claude --bypass-permissions

This is the mode for CI/CD pipelines and automation scripts where interactive prompts would hang execution. Only use it in isolated, disposable environments — Docker containers with no persistent state, ephemeral VMs, GitHub Actions runners that are discarded after each run.

Never use bypassPermissions on a developer machine with real files and credentials.

Administrators can lock this out organization-wide:

{
  "permissions": {
    "disableBypassPermissionsMode": "disable"
  }
}

Permission Rule Syntax

Rules live in the permissions block of any settings file:

{
  "permissions": {
    "allow": ["Bash(npm run *)", "Bash(git status)"],
    "ask": ["Edit(src/billing/**)"],
    "deny": ["Bash(git push *)"]
  }
}

Tool Matching

PatternEffect
BashMatches all Bash commands (bare name = match all)
Bash(*)Equivalent to Bash
Bash(npm run build)Matches the exact command npm run build
EditMatches all file edits
ReadMatches all file reads
WebFetchMatches all web requests

Bash Wildcards

* matches any sequence of characters including spaces:

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",          
      "Bash(git commit *)",       
      "Bash(git * main)",         
      "Bash(* --version)",        
      "Bash(* --help *)"          
    ],
    "deny": [
      "Bash(git push *)",         
      "Bash(npm publish *)"       
    ]
  }
}

Word boundary behavior: Bash(ls *) matches ls -la but not lsof. The space before * enforces that the prefix must be followed by a space or end-of-string. Without the space — Bash(ls*) — it matches both.

Compound commands: Bash(safe-cmd *) does NOT permit safe-cmd && other-cmd. Claude Code recognizes shell operators (&&, ||, ;, |) and checks each subcommand independently.

Process wrappers: Bash(npm test *) also matches timeout 30 npm test. Recognized wrappers that get stripped before matching: timeout, time, nice, nohup, stdbuf, and bare xargs.

The :* suffix: Bash(npm:*) is equivalent to Bash(npm *). Both match anything starting with npm .

Read and Edit Rules

File access rules use gitignore-style patterns with four anchoring modes:

PrefixMeaningExample
//Absolute from filesystem rootRead(//etc/hosts)
~/From home directoryRead(~/.zshrc)
/From project rootEdit(/src/**)
bare or ./From current directoryRead(*.env)
{
  "permissions": {
    "allow": [
      "Edit(/src/**)",            
      "Edit(/tests/**)"           
    ],
    "deny": [
      "Edit(/src/billing/**)",    
      "Read(//**/.env)",          
      "Read(~/.ssh/**)"           
    ]
  }
}

Pattern matching follows gitignore rules: * matches within a directory, ** matches recursively across directories.

Symlink handling: Allow rules apply when both the symlink and its target match. Deny rules apply when either matches. A symlink inside an allowed directory that points outside it still prompts.

WebFetch Rules

{
  "permissions": {
    "allow": ["WebFetch(domain:github.com)", "WebFetch(domain:npmjs.com)"],
    "deny": ["WebFetch"]
  }
}

This allows fetching from GitHub and npm, blocks all other WebFetch calls.

MCP Tool Rules

{
  "permissions": {
    "allow": ["mcp__github__*"],              
    "deny": ["mcp__github__push_commit"]     
  }
}

Format: mcp__<server-name>__<tool-name>. Wildcards work.

Agent (Subagent) Rules

{
  "permissions": {
    "deny": ["Agent(Explore)", "Agent(Plan)"]
  }
}

Agent(Explore) and Agent(Plan) disable those built-in subagents. Agent(my-agent) controls a custom agent.


Viewing and Managing Rules at Runtime

/permissions

This opens the permissions UI inside Claude Code, listing all active rules and the settings file they came from. Use it to:

  • See every rule at all scopes merged together
  • Understand why a specific tool is or isn’t being prompted
  • Add quick approvals without editing settings.json directly

Settings File Precedence

Rules compose across multiple settings files. The evaluation order from highest to lowest precedence:

  1. Managed policy settings — Cannot be overridden by anything below
  2. CLI arguments (--allowedTools, --disallowedTools) — Session-level only
  3. Local project settings (.claude/settings.local.json) — Per-developer, gitignored
  4. Shared project settings (.claude/settings.json) — Committed, team-shared
  5. User settings (~/.claude/settings.json) — Personal, all projects

Critical: A deny at any scope beats an allow at any scope. A user-level deny blocks a project-level allow. A managed policy deny blocks everything.

Settings File Locations

ScopeFileShared
User~/.claude/settings.jsonNo
Project (shared).claude/settings.jsonYes, commit it
Project (local).claude/settings.local.jsonNo, gitignore it
Managed policySee managed settings sectionDeployed via MDM

Enterprise: Managed Settings

Organizations that need centralized control over Claude Code deploy managed settings that developers cannot override.

Delivery Mechanisms

macOS (MDM): Write to the key com.anthropic.claudecode in the machine-level plist.

File-based: Create the managed-settings.json file at:

  • macOS: /Library/Application Support/ClaudeCode/managed-settings.json
  • Linux: /etc/claude-code/managed-settings.json
  • Windows: C:\Program Files\ClaudeCode\managed-settings.json

Managed-Only Settings

These settings are only honored when placed in managed settings. Placing them in user or project settings has no effect:

SettingEffect
allowManagedPermissionRulesOnlyWhen true, blocks user/project allow/ask/deny rules. Only managed rules apply
allowManagedMcpServersOnlyOnly MCP servers in managed settings load
allowManagedHooksOnlyOnly managed hooks run; user/project hooks are blocked
permissions.disableBypassPermissionsModeSet "disable" to block bypassPermissions mode
permissions.disableAutoModeSet "disable" to block auto mode

Typical Enterprise Configuration

{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "disableAutoMode": "disable",
    "deny": [
      "Bash(git push --force *)",
      "Bash(git push --force-with-lease *)",
      "Bash(curl * | bash)",
      "Bash(wget * | sh)",
      "Bash(rm -rf /*)",
      "Read(//**/.env)",
      "Read(~/.aws/**)",
      "Read(~/.ssh/**)"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "allowManagedHooksOnly": true,
  "allowManagedMcpServersOnly": true
}

This configuration:

  • Forces all permission rules to come only from managed settings
  • Blocks dangerous Bash patterns org-wide
  • Prevents developers from overriding with permissive local rules
  • Blocks credential file access

Extending Permissions with Hooks

Static rules handle known patterns. For dynamic permission logic, PreToolUse hooks run before permission evaluation:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/validate-bash.sh"
          }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# .claude/hooks/validate-bash.sh
# Blocks git push to protected branches

COMMAND=$(cat /dev/stdin | jq -r '.tool_input.command // ""')

if echo "$COMMAND" | grep -qE '^git push.*\b(main|master|production)\b'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Direct push to protected branch blocked. Create a PR instead."}}' 
  exit 2
fi

exit 0

Exit code 2 from a PreToolUse hook blocks the tool call before any permission rule evaluation. This applies even if an allow rule would permit the command.

Important interaction: Hooks cannot bypass deny rules. If a deny rule matches, it blocks regardless of what the hook returns. Hooks add logic on top of rules — they don’t replace them.

Use hooks when:

  • You need to inspect runtime state (current branch, file contents, environment variables)
  • The permission decision depends on context you can’t express with static patterns
  • You want to provide a specific error message explaining why something was blocked

Working Directories and Additional Directories

By default, Claude has read/write access to the directory where it was launched. Extend this with:

# Session-level
claude --add-dir ../shared-config --add-dir /mnt/data

# Persistent in settings
{
  "permissions": {
    "additionalDirectories": ["../shared-lib"]
  }
}

Files in additional directories follow the same permission mode as the working directory. Note: additionalDirectories in settings grants file access only — it doesn’t load .claude/ configuration from those directories. The --add-dir CLI flag does load configuration (skills, settings with limited keys).


How Sandboxing and Permissions Interact

Permissions and sandboxing (sandbox.enabled: true in settings) are complementary layers:

  • Permissions: Claude-level controls. Which tools Claude can use, which files Claude can access. Applied before Claude even attempts a tool call.
  • Sandboxing: OS-level controls on the Bash tool specifically. Even if Claude is permitted to run a Bash command, the sandbox limits what that command can do at the OS level.

With autoAllowBashIfSandboxed: true (the default when sandboxing is enabled), sandboxed Bash runs without prompting. The sandbox boundary replaces per-command prompts. Explicit deny rules still apply.

Use both for defense-in-depth: permissions prevent Claude from attempting restricted actions, sandboxing prevents Bash subprocesses from reaching restricted resources even if prompt injection bypasses Claude’s decision-making.


Practical Configurations

Solo Developer: Productive and Safe

{
  "defaultMode": "acceptEdits",
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(yarn *)",
      "Bash(git status)",
      "Bash(git log *)",
      "Bash(git diff *)",
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(git checkout *)",
      "Bash(git branch *)"
    ],
    "deny": [
      "Bash(git push --force *)",
      "Bash(git push --force-with-lease *)",
      "Bash(npm publish *)",
      "Read(~/.ssh/**)",
      "Read(//**/.env)"
    ]
  }
}

CI/CD Runner (Isolated Container)

{
  "defaultMode": "bypassPermissions"
}

Only appropriate because the CI runner is ephemeral and isolated. Nothing persists after the run.

Code Review Agent (Read-Only)

{
  "defaultMode": "plan",
  "permissions": {
    "allow": [
      "Bash(git log *)",
      "Bash(git diff *)",
      "Bash(git show *)",
      "WebFetch(domain:github.com)"
    ]
  }
}

Claude can read everything and analyze freely, but cannot modify any file.

Scoped Feature Agent

{
  "defaultMode": "dontAsk",
  "permissions": {
    "allow": [
      "Read(src/auth/**)",
      "Edit(src/auth/**)",
      "Bash(npm run test:auth)",
      "Bash(npm run lint src/auth/**)"
    ]
  }
}

Claude can only touch src/auth/, run auth tests, and lint auth files. Everything else is denied by dontAsk mode.


The Read-Only Command Exemption

Claude Code maintains a built-in list of Bash commands treated as read-only. These run without any permission prompt in every mode:

ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and read-only git commands.

You cannot add to this list. You can override it by adding explicit ask or deny rules:

{
  "permissions": {
    "deny": ["Bash(cat *)"]
  }
}

This would block cat even though it’s normally read-only.


Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules