Understanding Claude Code Permissions and the Security Model
AI generated
Claude
>_
Claude Code · Permissions · Security · Developer Workflow
Understanding Claude Code Permissions and the Security Model
Finding the right balance between safety and workflow speed

Claude Code asks for approval before every file edit and every Bash command, unless a developer deliberately configures different rules. This article explains the permission modes, uses settings.json and hook examples to show which actions can be safely automated, and where deliberate confirmation before destructive commands and remote pushes remains essential.

14 min. read Permission modes · settings.json · Hooks Claude Code CLI · Team Policies

1. Why the permission model sits at the core of Claude Code

Claude Code can read files, edit them, delete them, run Bash commands, and access the network, essentially anything the calling user is themselves authorized to do. That is exactly why the permission model is not a side feature but the central safety layer between a language-based assistant and a live development system. Without this layer, every suggestion would need to be trusted blindly before execution, a model that neither solo developers nor teams could sustainably work with.

The core idea is simple: low-risk, highly reversible actions, such as reading a file, should not slow down the workflow. High-risk or hard-to-reverse actions, such as deleting a directory or force-pushing, should force a deliberate human decision. This tradeoff between safety and speed runs through every configuration layer of Claude Code, from the command line to settings.json to project-wide hooks, all covered in detail in the sections below.

2. Permission modes at a glance: default, auto-accept, plan, bypass

Claude Code offers several permission modes that differ in their default stance toward asking for confirmation. The default mode asks before every file edit and every Bash command individually, with the option to approve a single action, all actions of that type, or all actions for the rest of the session. Auto-accept mode (acceptEdits) applies file edits automatically but still requires confirmation for Bash commands, useful for refactoring tasks that involve many small edits but no shell actions.

Plan mode executes no changes at all and only produces a plan for review, ideal for exploratory tasks or codebases that are still unfamiliar. Bypass mode (--dangerously-skip-permissions) disables all confirmation prompts entirely and should only be used in isolated environments such as containers or CI pipelines with no access to production systems. The name is deliberately chosen: enabling this mode in a normal development environment gives up the last layer of human control completely.


#!/usr/bin/env bash
# Claude Code CLI: permission mode per session

# Default mode: every file edit and every Bash command needs approval
claude

# Auto-accept file edits, but Bash commands still require approval
claude --permission-mode acceptEdits

# Planning only: no edits or commands are executed, only a plan is produced
claude --permission-mode plan

# Skip all permission prompts entirely (dangerous, use only in sandboxes/CI)
claude --dangerously-skip-permissions

# Headless mode with an explicit allow-list for a CI pipeline
claude -p "Update the changelog for this release" \
  --allowedTools "Read" "Grep" "Edit(CHANGELOG.md)"

3. What can run without asking: reading, searching, analyzing

Read-only operations are the clearest case for automation without confirmation. Read, Grep, and Glob change no state, are repeatable at will, and only return information that Claude Code uses to plan further steps. A developer who had to manually confirm every read operation would effectively negate the tool's core advantage: fast exploration of large codebases.

Pure analysis commands like static code checks (phpstan, phpcs) or running an existing test suite in an isolated environment also fall into this low-risk category, as long as they touch no production data or external systems. The important distinction is between a command that returns information and a command that produces a side effect: git status and git diff are harmless, git commit already changes project state and belongs in a different category. That distinction is the foundation of any sensible allow-list.

4. Approving file edits: Edit, Write, and MultiEdit

File edits sit in a gray zone between low-risk and high-risk. Within a Git repository they are fundamentally reversible through version control, a faulty edit can be inspected with git diff and reverted with git checkout as long as it hasn't been committed yet. That is exactly why auto-accept mode for file edits is acceptable in many teams, while Bash commands continue to require individual confirmation.

The safety boundary shifts, however, once edits touch files outside the working directory, involve configuration files containing credentials, or sit right before an automatic commit. In settings.json, edit rules can be scoped to specific paths, for example Edit(src/app/code/**) as an allow rule combined with an explicit ask rule for .env or *.pem files. This granularity allows production code to be edited freely while sensitive configuration files still require deliberate confirmation.

5. Controlling Bash commands: allow, ask, and deny rules

Bash commands are the most powerful and simultaneously the riskiest tool category, because they give Claude Code access to the entire system, not just individual files. Claude Code therefore distinguishes three rule levels: allow for commands that run without confirmation, ask for commands that require explicit confirmation, and deny for commands that are blocked regardless of context. Deny rules always take precedence over allow rules, a command matching both lists gets blocked.

In practice, a combination works well: generous allow rules for recurring, harmless commands (npm test, git status, bin/magento cache:status) paired with explicit deny rules for known danger patterns. Wildcard patterns like Bash(git push --force*) also catch variants with extra flags. Important: an allow-list is not a substitute for a deny-list, since it only covers known-good cases, unknown commands land in ask mode either way and must be deliberately reviewed there.


{
  "permissions": {
    "allow": [
      "Read",
      "Grep",
      "Glob",
      "Bash(npm test)",
      "Bash(php bin/phpunit *)",
      "Bash(git status)",
      "Bash(git diff*)",
      "Bash(git log*)"
    ],
    "ask": [
      "Bash(npm install*)",
      "Bash(git commit*)",
      "Bash(git push*)",
      "Edit",
      "Write"
    ],
    "deny": [
      "Bash(rm -rf*)",
      "Bash(git push --force*)",
      "Bash(git reset --hard*)",
      "Bash(DROP TABLE*)"
    ]
  }
}

6. Git and remote operations: when a pause is mandatory

Git operations deserve separate treatment because they differ in whether their impact stays local or reaches the whole team. A local commit only affects the working directory and can be corrected with git reset or git commit --amend as long as it hasn't been pushed. A git push to a feature branch already has broader reach, but is usually still reversible. A git push --force on a shared branch or directly on main, however, overwrites history for the entire team and can irreversibly lose other people's commits.

The practical consequence: local commits can fall into the auto-accept bucket in trusted projects, while every push command should generally stay in the ask category, regardless of how routine a given workflow appears. Especially critical are commands that rewrite history (rebase -i on shared branches), delete tags, or remove remote branches. These operations are, for good reason, explicitly on the deny-list in many team configurations, even when an individual developer occasionally needs them deliberately, in which case running them manually outside Claude Code is the safer choice.

7. settings.json and hooks: enforcing rules project-wide

While permission modes control the behavior of a single session, the rules in .claude/settings.json apply project-wide and reload on every start. This file can be versioned and thus maintained alongside the code in the repository, changes to the security policy go through the same code review as any other change. For cases that go beyond static allow/deny patterns, Claude Code offers hooks: scripts that run automatically before (PreToolUse) or after (PostToolUse) a tool execution and can block the call based on arbitrary logic.

A PreToolUse hook receives the planned tool call as JSON on stdin and can prevent execution with a specific exit code. This allows checks that pure pattern matching cannot cover, for example forbidding write access outside a specific directory regardless of the exact command text. Hooks are therefore the more flexible, but also more maintenance-intensive, complement to the declarative rules in settings.json.


#!/usr/bin/env python3
"""PreToolUse hook: block destructive Bash commands before execution.
Reads the tool call as JSON from stdin, exits 2 to block with a message.
"""
import json
import re
import sys

DANGEROUS_PATTERNS = [
    r"rm\s+-rf\s+/(?!home|tmp)",
    r"git\s+push\s+.*--force",
    r"git\s+reset\s+--hard",
    r"DROP\s+TABLE",
    r">\s*/dev/sd[a-z]",
]

payload = json.load(sys.stdin)
if payload.get("tool_name") != "Bash":
    sys.exit(0)

command = payload.get("tool_input", {}).get("command", "")

for pattern in DANGEROUS_PATTERNS:
    if re.search(pattern, command, re.IGNORECASE):
        print(f"Blocked: command matches dangerous pattern '{pattern}'", file=sys.stderr)
        sys.exit(2)

sys.exit(0)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/block-dangerous-commands.py"
          }
        ]
      }
    ]
  }
}

8. Team policies: shared configuration and onboarding

Once several developers use Claude Code on the same project, an individual setting becomes a team question. A project-wide .claude/settings.json in the repository ensures everyone gets the same baseline rules without configuring them individually, new team members inherit the policy automatically when they clone the repository. Personal additions can be kept separately in a non-versioned settings.local.json, for example for individual credentials or project-unrelated helper tools.

For teams with varying experience levels, a conservative baseline configuration that is loosened gradually is preferable to the reverse. It's also critical to decide who can approve changes to the permission configuration itself: if settings.json can be changed through a normal pull request, that file should be subject to the same review requirements as deployment scripts or CI configuration, since it directly defines which automated actions may run on the team's behalf.


// ci-review.js: invoke Claude Code headlessly with a strict allow-list
const { spawnSync } = require('node:child_process');

const result = spawnSync('claude', [
  '-p', 'Review the diff for obvious bugs and report findings only',
  '--permission-mode', 'plan',
  '--allowedTools', 'Read', 'Grep', 'Glob',
], { encoding: 'utf-8' });

if (result.status !== 0) {
  console.error('Claude Code review failed:', result.stderr);
  process.exit(1);
}

console.log(result.stdout);

9. Limits of the model: false sense of security and residual risk

The permission system reduces risk but does not eliminate it entirely. An allow rule like Bash(npm install*) looks harmless but can install a package with a malicious postinstall script without triggering any further confirmation. Prompt injection through content Claude Code reads while working, such as a manipulated README file or a compromised comment in a dependency, can also try to steer the model toward actions outside the actual task. The deny-list is therefore no substitute for basic caution with unfamiliar dependencies or repositories.

Realistically, the permission model is one defense layer among several, not the only one. Sandboxing at the operating system or container level, restricted database permissions for development environments, and regular audits of the settings.json rules complement the built-in safeguards. The overview below classifies typical actions by whether automatic approval is acceptable or deliberate confirmation remains mandatory.

Action Classification Reasoning
Reading files (Read, Grep, Glob) Safe to automate Changes no state, repeatable at any time
Running local tests Safe to automate Runs isolated, no production data involved
File edits in the working directory Automatable with review Always inspectable via git diff before commit
rm -rf or git clean -fd Confirmation required Irreversibly deletes data outside version control
git push --force / push to main Confirmation required Overwrites remote history, affects the whole team
Production migrations and deploys Confirmation required Not reversible, affects live systems and real users

Mironsoft

Magento and Hyvä development with AI-assisted workflows

Ready to integrate Claude Code safely into your workflow?

We set up permission rules, hooks, and team policies for Claude Code so automation speeds up delivery without giving up control over production Magento systems.

Permission audit

Review existing settings.json and hooks for gaps

Team configuration

Set up a shared policy for all developers and CI pipelines

CI integration

Integrate Claude Code safely into pipelines in plan mode

10. Summary

The Claude Code permission system addresses a fundamental problem of AI-assisted development: automation should speed up delivery without giving up control over production systems. Default mode asks before every file edit and every Bash command, auto-accept mode speeds up file edits, plan mode executes nothing at all, and bypass mode belongs exclusively in isolated environments. Read-only operations can be automated without hesitation, while destructive commands like rm -rf, git push --force, and production migrations generally deserve deliberate confirmation.

Project-wide rules in settings.json and flexible PreToolUse hooks make this policy reproducible and team-wide, rather than dependent on individual settings. It remains important to stay aware that the permission model reduces risk but does not eliminate it, prompt injection and manipulated dependencies require additional caution beyond pure allow and deny lists.

Claude Code Permissions - The Essentials at a Glance

Permission modes

Default asks about everything, acceptEdits speeds up edits, plan executes nothing, bypassPermissions only in sandboxes.

Safe to automate

Reading, searching, and local tests change no state and can run without confirmation.

Confirmation required

rm -rf, git push --force, and production migrations are not reversible and need deliberate confirmation.

Team policy

Versioned settings.json plus hooks make the rules reproducible and consistent across the team.

11. FAQ: Claude Code Permissions and the Security Model

1What is the difference between default, acceptEdits, plan, and bypassPermissions?
Default asks about everything, acceptEdits speeds up file edits, plan executes nothing, bypassPermissions disables all confirmations and belongs only in isolated environments.
2Which actions can I safely auto-approve?
Read, Grep, Glob, and isolated test runs change no state and are repeatable at any time, the best candidates for a permanent allow rule.
3Why should git push --force never run automatically?
Overwrites remote history and can irreversibly remove other people's commits, the damage affects the whole team, not just the local workspace.
4How do hooks compare to allow/deny rules?
Allow/deny are declarative pattern matches. Hooks are scripts with arbitrary logic that run before or after a tool execution and allow more complex checks.
5Can I run Claude Code with no confirmations at all?
Technically yes via --dangerously-skip-permissions, but only sensible in isolated containers or sandboxes without access to critical data.
6How should teams manage a shared permission policy?
Via a versioned .claude/settings.json in the repository with the same review requirements as deployment scripts.
7What happens on a deny/allow conflict?
Deny always wins. A command matching both patterns gets blocked, so no danger pattern is ever accidentally permitted.
8Does the system protect against prompt injection?
Only partially. It limits tool access but doesn't stop manipulated file content from steering Claude Code toward permitted but unwanted actions, caution remains necessary.
9Should I auto-approve database migrations?
Not in production, since migrations are often hard to reverse. In local databases with backups, it may be acceptable depending on risk tolerance.
10How do I test my settings.json rules?
With targeted test calls in plan mode and a separate test script that exercises hooks with sample payloads independently of Claude Code.