Claude Permissions
Model
Tiers: allow / ask / deny. Evaluated deny → ask → allow; the first match wins and specificity doesn't matter: a broad Bash(aws *) deny beats a narrow Bash(aws s3 ls) allow. A PreToolUse hook can't bypass deny/ask rules, but a hook that exits 2 blocks even an allowed call. Allow/deny lists merge across settings files.
{
"permissions": {
"allow": ["Read(**)", "Write(./src/**)", "Bash(npm run *)"],
"ask": ["Bash(git commit *)"],
"deny": ["Bash(curl *)", "Read(~/.ssh/**)", "WebFetch(domain:bad.com)", "mcp__stripe", "Agent(Explore)"],
"additionalDirectories": ["../shared-docs/"],
"defaultMode": "acceptEdits",
"disableBypassPermissionsMode": "disable"
}
}
Rule syntax
| Target | Pattern | Notes |
|---|---|---|
| Bash | Bash(git * main), Bash(* --version) | * anywhere; space matters: Bash(ls *) matches ls -la, not lsof. Trailing :* is equivalent (Bash(ls:*)) and only valid at the end. Same shape for PowerShell(...) |
| Read / Edit | gitignore-style globs | see prefixes below |
| WebFetch | WebFetch(domain:github.com) | exact host only; domain:*.example.com covers subdomains but not the apex. Bare WebFetch denies/asks every fetch, unlike domain:* |
| MCP | mcp__server, mcp__server__*, mcp__server__tool | |
| Subagent | Agent(Explore) | Also parameter forms like Agent(isolation:*) |
| Skill | Skill(commit), Skill(deploy *) | deny Skill to disable all skills |
| Bare name | Read, Edit, Bash, WebFetch, mcp__stripe | no parentheses = everything |
Path prefixes for Read/Edit:
| Prefix | Meaning | Example |
|---|---|---|
// | Absolute from filesystem root | Read(//etc/hosts) |
~/ | Home | Edit(~/.zshrc) |
/ | Project root | Read(/src/**/*.ts) |
none or ./ | Current working dir | Read(*.env) |
Modes (permissions.defaultMode)
| Mode | Runs without asking |
|---|---|
default (Manual) | Reads only |
acceptEdits | Reads, file edits, simple fs commands (mkdir, mv, cp) inside the working dir |
plan | Reads only; no changes |
auto | Everything, with a background classifier reviewing actions |
dontAsk | Only pre-approved tools; anything that would prompt is denied (CI/scripts) |
bypassPermissions | Everything (isolated containers/VMs only) |
claude --permission-mode plan·/permissionsedits rules.Shift+Tabcycles Manual → Edit automatically → Plan → (Bypass if enabled) → Auto if available.dontAsknever appears in the cycle.--allow-dangerously-skip-permissionsadds bypass to the cycle without activating it.autoandbypassPermissionscan't be set from project or local settings, only user/managed or--permission-mode.- Deny rules apply in every mode, including bypass. Writes to
.git,.claude,.bashrcare never auto-approved except in bypass.
--dangerously-skip-permissions
claude --dangerously-skip-permissions
claude --dangerously-skip-permissions -p "Fix all lint errors"
Use only in devcontainer/VM, CI, or very low-risk scoped work. Block it with disableBypassPermissionsMode: "disable".
Danger: with auto-approve, Claude can decide the sandbox is the obstacle and turn it off; npx can fetch and run arbitrary code, so a deny rule on it isn't enough.
Sandbox
Off by default. Enable with /sandbox or "sandbox": {"enabled": true}.
- macOS Seatbelt · Linux/WSL2 bubblewrap +
socat· native Windows: not supported (use WSL2). Built on@anthropic-ai/sandbox-runtime. - Covers shell commands only (Bash, PowerShell, Monitor) and their child processes. Not Read/Edit/Write/WebFetch/WebSearch, MCP servers, hooks, LSP, status line.
- Modes: auto-allow (sandboxed commands run without a prompt; deny rules and risky
rm/git pushasks still prompt) or regular permissions. - Failed sandboxed commands may retry unsandboxed (
dangerouslyDisableSandbox); block withallowUnsandboxedCommands: false.failIfUnavailableexits instead of running unsandboxed. - Network goes through a local proxy with a domain allowlist that starts empty:
network.allowedDomains/deniedDomains. Tools ignoring proxy vars (plain ssh, most DB drivers) can't connect; UDP/ICMP blocked. - Filesystem: writes allowed in the working dir and temp; reads almost everywhere. Tune with
filesystem.allowWrite/denyWrite/allowRead/denyRead;excludedCommandsrun outside the sandbox. credentials.files/envVarscandenyormasksecrets.- Limits: not a complete boundary. The proxy decides from the hostname without inspecting TLS, so broad domains like
github.comcan enable exfiltration (domain fronting).allowUnixSockets(e.g.docker.sock), writable$PATHor shell-rc paths,enableWeakerNestedSandbox(Docker) and macOSallowAppleEventseach weaken it. Subagents share the parent's sandbox config; computer use is not sandboxed.
Dev containers
Install via the Dev Container Feature: "features": { "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {} } in .devcontainer/devcontainer.json (works in VS Code, Codespaces, JetBrains, Cursor). The :1.0 pins the install script, not Claude Code.
- Persist login: mount a volume at
~/.claudeand setCLAUDE_CONFIG_DIRto it (otherwise~/.claude.jsonis lost on rebuild). - Policy: copy
managed-settings.jsonto/etc/claude-code/in the Dockerfile (engineers with repo write access can still change that step; use server-managed settings for hard policy). - The reference container in
anthropics/claude-codeaddsinit-firewall.sh(needsNET_ADMIN/NET_RAW) to limit egress. --dangerously-skip-permissionsis rejected when run as root, so use a non-rootremoteUser. A container doesn't stop exfiltration of anything inside it (including~/.claudecredentials), so use trusted repos and avoid mounting~/.ssh.
Headless tool limits
claude -p "run tests" --allowedTools "Bash(npm run test:*)" "Read"
claude -p "analyze" --disallowedTools "Bash(rm:*)" "Write"
Related: Claude Settings, Claude Hooks and Automation, Claude Fundamentals