Skip to main content

Module 7 — Permissions, execution modes, sandbox and settings files

Module 6 showed that a skill pre-approves a few tools for one turn. This module handles the layer beneath: the general rules that decide, for the whole session, what Claude can read, write, execute and reach. It is the layer to put down as soon as you open a repo: it protects .env, lets make test run without prompting, isolates Bash commands with the sandbox, and decides who approves Claude's actions — you, a classifier, or nobody.

The permission model

A permission rule has the form Tool or Tool(specifier). It lives in a permissions.allow, permissions.ask or permissions.deny list of a settings file, or in a CLI flag (--allowedTools, --disallowedTools, --permission-mode). Rules are evaluated deny first, then ask, then allow; the first match wins. A deny has no exception; a general ask forces the question even if a more specific allow matches.

FormEffect
Bash or Bash(*)Any Bash command. In deny, removes the tool from the context.
Bash(npm run *)Any command starting with npm run, including npm run on its own.
Bash(git *)Any git command — also covers git push.
Edit(app/**)Editing under app/ (gitignore-style, anchored to the source of the settings).
Edit(/src/**)Anchored to the project root in project settings. A single // anchors to the system root.
Read(./.env)Blocks reading the current .env; also blocks Edit and Write on that path (v2.1.208+/v2.1.228+).
WebFetch(domain:example.com)Fetch to example.com. domain:*.example.com covers subdomains.
mcp__github__get_*MCP tools whose name starts with get_ on the github server.
Agent(Explore)Subagent named Explore.
Cd(~/code/**)Authorized target for /cd. An allow flips /cd into allowlist mode.

Two rules to remember about Bash: * matches any text including spaces, and Claude Code knows about shell operators — a Bash(safe-cmd *) in allow does not cover safe-cmd && rm -rf .. The recognized separators are &&, ||, ;, |, |&, & and the newline. For > file, the target path goes through the Edit rules and the protected paths.

The /permissions command opens an interactive dialog where you see every rule, its scope and its source file; edits take effect on the next tool call in the same turn (v2.1.234+).

The six execution modes

The execution mode decides who approves tool calls that rules do not settle. Six values are exposed via permissions.defaultMode or the --permission-mode flag.

ModeWhat runs without asking
default (labeled Manual)Read-only actions. Asks on every edit and every Bash outside the built-in read-only list.
acceptEditsReads, edits inside the working directories, plus mkdir, touch, mv, cp, rm, rmdir, sed.
planRead and exploration only, no edits. The classifier lets shell commands through if auto is available.
autoEverything, with a classifier (small model) that refuses dangerous actions. Default on Pro/Max/Team since v2.1.228 (macOS/Linux/WSL).
dontAskOnly explicit allow rules and read-only commands; everything else refused. Ideal for CI.
bypassPermissionsEverything, except a few guards (critical paths). Reserve for isolated containers. --dangerously-skip-permissions is the equivalent.

Shift+Tab cycles modes in the CLI; auto appears when available; bypassPermissions only appears after an explicit launch. In managed settings, permissions.disableBypassPermissionsMode and permissions.disableAutoMode set to "disable" block bypass and auto respectively. The auto value in defaultMode does not take effect in .claude/settings.json or .local.json — it must sit in ~/.claude/settings.json or in managed settings.

Protected and critical paths

Two sets of paths are never auto-approved except in bypassPermissions.

  • Protected paths.git, .claude (except .claude/worktrees), .vscode, .idea, .husky, .devcontainer, .gitconfig, .bashrc, .zshrc, .npmrc, .mcp.json, .claude.json, etc. A write asks in Manual/acceptEdits, goes through the classifier in auto, is refused in dontAsk.
  • Critical paths for rm/rmdir — root /, top-level directories (/usr, /etc…), home directory, C:\, current directory and its parents. Neither an allow nor a PreToolUse hook can approve an rm -rf on those paths.

The Bash sandbox

The sandbox is the OS complement to the rules: it applies a filesystem and network firewall to each shell command and its children. On macOS it uses Seatbelt (nothing to install); on Linux and WSL2 it relies on bubblewrap and socat (to install). Native Windows is not supported — go through WSL2.

/sandbox opens a three-tab panel (Mode, Overrides, Config), plus Dependencies if a package is missing. The choice lands in .claude/settings.local.json. To enable everywhere, sandbox.enabled: true in ~/.claude/settings.json.

Two sandbox modes, independent of the permission mode:

  • Auto-allow: sandboxable commands run without prompting; the others (host not allowlisted, incompatibility) fall back into the normal flow. A Bash(*) in ask is ignored for sandboxed commands (except in plan).
  • Regular permissions: the sandbox isolates, but every command goes through the permission flow.

By default, a sandboxed command writes to the working directory, the session tmp, and added directories (--add-dir, permissions.additionalDirectories). Widen with sandbox.filesystem.allowWrite; narrow with denyWrite, denyRead. The network is allowlisted per domain via sandbox.network.allowedDomains, whose entries merge across scopes instead of replacing.

A command that truly cannot be sandboxed can be retried with the dangerouslyDisableSandbox parameter ("unsandboxed retry"); in Manual it asks, in auto the classifier judges. To close that escape hatch: sandbox.allowUnsandboxedCommands: false.

Settings files and precedence

Claude Code reads its configuration from five sources, in strict order — the first that defines a key wins.

RankSourcePath or originScope
1Managedmanaged-settings.json, MDM, claude.ai consoleOrganization (irreplaceable, with exceptions)
2CLI --settingsInline JSON or fileThis session
3Project local.claude/settings.local.jsonYou, this project (not committed)
4Project shared.claude/settings.jsonWhole team (committed)
5User~/.claude/settings.jsonYou, all your projects

Environment variables are not a level: for each behavior, the docs state whether the variable wins (ANTHROPIC_MODEL wins over model) or loses (ANTHROPIC_DEFAULT_MODEL only takes effect if no file defines model). A deny in any scope blocks an allow in another: the precedence deny > ask > allow applies across scopes; --allowedTools cannot contradict a managed deny.

Main permissions keys: allow, ask, deny, additionalDirectories, blockReadsOutsideWorkingDirectories, defaultMode, disableBypassPermissionsMode, disableAutoMode. allowManagedPermissionRulesOnly restricts rules to managed settings. Other useful keys: apiKeyHelper (shell command that generates the API key), awsAuthRefresh (refreshes Bedrock), env (injected variables), skillOverrides (skill visibility), sandbox.*. The command /config key=value (v2.1.181+) writes a key without opening the menu.

A committed .claude/settings.local.json loses its personal status and becomes a project source; its rules stay pending until workspace trust is granted. Same if .claude is a symlink.

Key environment variables

  • CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 — disables background subagents and background: true.
  • CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 — scrubs the environment of subprocesses (useful in the Linux sandbox).
  • CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1 — enable Bedrock/Vertex and expose /setup-bedrock, /setup-vertex.
  • CLAUDE_CONFIG_DIR — moves the configuration directory out of ~/.claude.
  • DISABLE_DOCTOR_COMMAND=1 — hides /doctor.

Running example: locking down Kiosque

After three modules on Kiosque without a safety net, we put permissions in place. The shared file (.claude/settings.json, committed) defines what the whole team must have; the personal local file tunes.

acme-kiosque/.claude/settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "acceptEdits",
"additionalDirectories": ["../kiosque-migrations"],
"allow": [
"Bash(make test)",
"Bash(make lint)",
"Bash(make dev)",
"Bash(pytest *)",
"Bash(ruff *)",
"Bash(mypy *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(gh pr *)",
"Edit(app/**)",
"Edit(tests/**)",
"Edit(web/src/**)",
"WebFetch(domain:fastapi.tiangolo.com)",
"WebFetch(domain:docs.python.org)"
],
"ask": [
"Bash(git push *)",
"Bash(alembic upgrade *)"
],
"deny": [
"Read(./.env)",
"Read(./secrets/**)",
"Edit(./migrations/**)",
"Bash(rm -rf *)",
"Bash(git push --force*)",
"Bash(git push -f *)",
"Bash(git reset --hard *)"
]
},
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["*.pypi.org", "registry.npmjs.org", "*.github.com"]
}
}
}

Karim opens the project and accepts workspace trust. In pure Manual the day would be painful; acceptEdits lets edits in the working directories through. Force pushes are blocked outright, a normal git push asks, and SQL migrations remain blocked on edit. Lea has her personal file for the front-end:

acme-kiosque/.claude/settings.local.json
{
"permissions": {
"allow": ["Bash(npm run *)", "Bash(pnpm *)", "WebFetch(domain:vitejs.dev)"]
}
}

A week later, a /permissions audit shows twelve blocks on Bash(alembic downgrade *). We extend the ask list rather than open everything, and commit. When in doubt, claude doctor lists rejected rules and --debug explains a refusal.

A committed .claude/settings.local.json changes status

Once committed (or .claude used as a symlink), Claude Code treats it as a project source and holds its rules until workspace trust is granted — protection against a repo that would grant itself Bash(*).

Summary

  • Permission rules have the form Tool(specifier) and are evaluated deny > ask > allow; a deny in one scope blocks an allow in another.
  • The six modesdefault (Manual), acceptEdits, plan, auto, dontAsk, bypassPermissions — decide who approves. Shift+Tab cycles, --permission-mode sets on launch.
  • Protected paths (.git, .claude, .env, .mcp.json…) and critical paths (rm -rf /, ~, current folder) are never auto-approved, except in bypassPermissions.
  • The Bash sandbox applies an OS firewall to shell commands and their children: sandbox.enabled, sandbox.filesystem.allow/denyWrite, sandbox.network.allowedDomains. Two modes: auto-allow and regular.
  • Settings precedence: managed > CLI --settings > project local > project shared > user. A defaultMode: "auto" in .claude/settings.json does not take effect — it must live in user or managed.
  • /permissions tunes live, /status shows the loaded sources, claude doctor lists rejected rules.

Next module: Plan mode, checkpoints, sessions and worktrees: working without fear — the conversation-, code- and git-side safety nets that make experimentation reversible.