Key Principle
Claude Code's safety is defense in depth — independent layers, each closing a gap the layer above leaves open:
- Managed settings (org policy) — the outermost ring; overrides local developer config and is the only layer a developer cannot edit away (p. Chunk 001).
- Permission rules — allow / ask / deny which tool or command runs (p. Chunk 001, 014).
- Sandboxing — OS-level filesystem + network isolation gating what a tool's process can actually touch (p. Chunk 001, 014).
- Auto mode / the classifier — a second gate that runs after permissions, judging whether an allowed action risks data exfiltration (p. Chunk 013, 012).
- Hooks — deterministic scripts that guarantee an action happens (covered in implementation-playbook).
Why This Matters
Each layer exists because the one above has a hole. Without managed settings, every control is per-developer and can be relaxed locally (p. Chunk 001). Permissions vs. sandboxing — why you need both: denying WebFetch blocks Claude's fetch tool, but if Bash is allowed, curl/wget still reach any URL; sandboxing closes that gap with an OS-enforced domain allowlist (p. Chunk 001). And only permissions.deny is unconditional — it blocks before the classifier is consulted and cannot be overridden; a soft_deny prose rule you assumed was a hard wall can be cleared by user intent or an allow exception (p. Chunk 013).
Good Examples
- Org policy with merge-safe arrays. Managed values override user/project settings, but array settings like
permissions.allow/permissions.denymerge entries from all sources — developers can extend managed lists but cannot remove managed entries. Verify with/status→ Status tab →Setting sourcesshowingEnterprise managed settingsand its source(remote)/(plist)/(HKLM)/(file)(p. Chunk 001). - Close the Bash-curl gap with the sandbox. Enable
/sandbox(orsandbox.enabled) and scope the network withsandbox.network.allowedDomains(anddeniedDomains) so Claude works freely inside defined boundaries whilecurl-via-Bash can only reach allowlisted hosts (p. Chunk 001, 014). - Teach the classifier your trusted infra. Out of the box the classifier trusts only the working dir and the current repo's remotes, so pushing to your org's source control is blocked until added. The only field most orgs need is
autoMode.environment, written as natural-language prose. Include the literal"$defaults"to extend rather than replace:
{ "autoMode": { "environment": [
"$defaults",
"Source control: github.example.com/acme-corp and all repos under it",
"Key internal services: Jenkins at ci.example.com, Artifactory at artifacts.example.com"
] } }(p. Chunk 013, 012)
Counterpoints
- The
"$defaults"splice gotcha — omitting it replaces, not extends. Setting any auto-mode section without"$defaults"silently discards that section's built-in list:soft_denywithout it loses force-push /curl | bash/ production-deploy rules;hard_denywithout it loses data-exfiltration and auto-mode-bypass rules. Sections are independent. To take ownership safely: runclaude auto-mode defaults, copy the rules in, review each, then edit (p. Chunk 013). hard_denyis not a hard wall for tool patterns. The classifier's 4-tier precedence ishard_deny>soft_deny>allow> explicit user intent — but it runs inside the classifier, after permissions. For a tool-pattern block that must never run regardless of intent, usepermissions.deny, nothard_deny(p. Chunk 013).- "Clean up the repo" does not authorize force-push. Explicit user intent overrides remaining soft blocks only when the message directly and specifically describes the exact action — "force-push this branch" counts; general requests don't (p. Chunk 013).
- The classifier ignores
autoModein shared project settings (.claude/settings.json) — so a malicious checked-in repo can't self-grant trust. PutautoModein user, local, or managed settings (p. Chunk 012).
Key Commands & Config
/permissions Manage allow/ask/deny; "Recently denied" tab — press r to retry on exit
/sandbox Enable OS-level filesystem/network isolation
permissions.allow / .ask / .deny (.deny is the only unconditional block)
permissions.disableBypassPermissionsMode , allowManagedPermissionRulesOnly
sandbox.enabled , sandbox.network.allowedDomains / .deniedDomains
autoMode.environment Prose list of trusted infra ("$defaults" to extend)
autoMode.allow / .soft_deny / .hard_deny Prose arrays that REPLACE built-in rule lists
claude auto-mode defaults Print built-in environment/allow/soft_deny/hard_deny as JSON
claude auto-mode config Print effective rules ("$defaults" expanded) — run after saving
claude auto-mode critique AI review of custom rules (ambiguous/redundant/false-positive)
PermissionDenied hook React to denials programmaticallyRules of Thumb
- For an unconditional, non-negotiable block, use
permissions.denyin managed settings — nothing else is a hard wall. - Permissions gate which tool; sandboxing gates what the process touches. Use both.
- Always splice
"$defaults"when editing anyautoModesection, then runclaude auto-mode configto confirm effective rules. - Repeated denials for the same destination usually mean missing context — add it to
autoMode.environment.
Related References
- Claude Code Core Framework - where layered safety sits in the framework
- Parallel Orchestration — Decision Guide - permissions fan out to teammates/sessions
- Claude Code Implementation Playbook - hooks as the deterministic layer
- Claude Code Rules of Thumb - quick safety heuristics
Diagram
