AC
Chapter 06Scaling Up

Permissions and sandboxing

Allowlisting safe commands. OS-level isolation. When to skip permissions.

20 minLesson 4 of 4

Claude Code asks permission before running commands, editing files, and accessing external services. Every time it wants to run pnpm build, it asks. Every time it wants to edit a file, it asks. Every time it wants to run a bash command, it asks.

This is by design. An autonomous agent with unrestricted access to your file system and terminal is dangerous. One hallucinated rm -rf and your project is gone. One misunderstood instruction and it force-pushes to main. Permissions are the safety net.

But permissions also slow you down. If you approve pnpm build fifty times a day, that is fifty interruptions. The goal is to find the right balance: fast enough to stay in flow, safe enough to prevent disasters.

Permission modes

Claude Code has four permission levels. Each trades safety for speed.

Default

Asks for each action. Every file edit, every bash command, every tool call requires your approval. Safest, but slowest. You will spend more time approving than thinking.

Plan mode (Shift+Tab)

Claude proposes a full plan before acting. You review the plan, approve it, and Claude executes everything at once. Good for multi-step tasks where you want to see the whole picture first.

Auto mode

Claude runs freely within built-in safety limits. It will not delete files, force push, or install unknown packages. But it will edit files, run builds, and execute safe commands without asking. Good for routine development.

bypassPermissions

Full autonomy. No prompts for anything. Claude runs whatever it decides to run. Only use this for well-scoped, trusted tasks where you have other safety nets in place.

Most developers start at Default and stay there too long. They get used to the approval rhythm and never realize how much it slows them down. The goal is to move along the spectrum as your trust and guardrails increase.

Allowlisting safe commands

Some commands are always safe. pnpm build does not delete files. pnpm lint does not modify code. pnpm test does not push to production. You approve these every single time, without thinking. That is a signal: they should be pre-approved.

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Bash(pnpm build)",
      "Bash(pnpm lint)",
      "Bash(pnpm test*)",
      "Bash(pnpm tsc --noEmit)",
      "Bash(git status)",
      "Bash(git diff*)",
      "Bash(git log*)"
    ]
  }
}

With this configuration, Claude runs builds, lints, type checks, and git reads without asking. Everything else still requires approval. You eliminate the noise while keeping the guardrails on the things that matter.

You can use wildcards. Bash(pnpm test*) covers pnpm test, pnpm test:e2e, pnpm test -- --run contact. Bash(git log*) covers any git log variant. Be specific enough to be safe, broad enough to be useful.

Hook-based guardrails

This is an ECC pattern that gives you the best of both worlds: broad permissions with surgical restrictions. Instead of pre-approving everything and hoping for the best, you pre-approve most things but add hooks that block specific dangerous actions.

Hooks run before or after specific tool calls. A PreToolUse hook can inspect what Claude is about to do and block it. This means you can run in Auto mode — fast, no interruptions — while still having hard blocks on the things that must never happen.

.claude/hooks/block-dangerous.sh
#!/bin/bash
# PreToolUse hook: block dangerous operations

# Read the tool use from stdin
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Block force pushes
if [[ "$COMMAND" == *"push --force"* ]] || [[ "$COMMAND" == *"push -f"* ]]; then
  echo '{"decision": "block", "reason": "Force push blocked by hook"}'
  exit 0
fi

# Block writes to .env files
if [[ "$TOOL" == "Edit" ]] || [[ "$TOOL" == "Write" ]]; then
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
  if [[ "$FILE" == *".env"* ]]; then
    echo '{"decision": "block", "reason": "Writes to .env files blocked by hook"}'
    exit 0
  fi
fi

# Block rm -rf
if [[ "$COMMAND" == *"rm -rf"* ]]; then
  echo '{"decision": "block", "reason": "rm -rf blocked by hook"}'
  exit 0
fi

# Allow everything else
echo '{"decision": "allow"}'
.claude/settings.json — with hooks
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hook": ".claude/hooks/block-dangerous.sh"
      }
    ]
  }
}

Now Claude runs in Auto mode with nearly zero friction, but force pushes, .env edits, and rm -rf are physically blocked. The hook fires before every tool call, inspects it, and either allows or blocks it. You get speed and safety.

OS-level isolation

For maximum security, run Claude inside a container or VM. The agent can do anything inside the sandbox without affecting your host system. Docker makes this straightforward.

Running Claude in a container
# Build a container with your codebase
docker run -it --rm \
  -v $(pwd):/workspace \
  -w /workspace \
  node:20 bash -c "npm install -g @anthropic-ai/claude-code && claude"

# Or use a dedicated Dockerfile with your toolchain
docker build -t claude-sandbox .
docker run -it --rm -v $(pwd):/workspace claude-sandbox

Inside the container, Claude has full access to the mounted workspace but zero access to your host file system, SSH keys, environment variables, or other projects. If something goes catastrophically wrong, you delete the container and nothing is lost.

For teams that run Claude Code across multiple platforms — Cursor, Codex, OpenCode — there is a DRY adapter pattern: keep the source of truth (rules, agents, skills) in a root directory, and let platform-specific directories mirror only what each surface needs. Changes flow from root to platform copies. This prevents the drift that happens when you maintain four separate configurations that slowly diverge.

Container isolation is overkill for daily development. But it is essential for two scenarios:

  1. Running untrusted prompts. If someone else writes the prompt (a script, a webhook, an automated trigger), you cannot be sure what it will do. Sandbox it.
  2. Automated pipelines. In CI/CD, Claude runs without human supervision. Containers limit the blast radius of any mistake.

The permission dial

Think of permissions as a dial, not a switch. You do not go from "ask everything" to "trust everything" in one step. You move gradually as your guardrails improve.

Learning

Default mode. Ask everything. Watch what Claude does. Understand the patterns. Build intuition for what is safe and what is not.

Daily development

Auto mode with allowlisted commands. Pre-approve builds, lints, and tests. Claude handles routine work without interruption. You review edits occasionally.

Trusted routine tasks

bypassPermissions with hook guardrails. Claude runs fully autonomous, but hooks block the specific dangerous actions. You have defined the boundaries precisely.

Automated CI/CD

Container sandbox with restricted allowedTools. Maximum isolation. Minimal permissions. The agent does exactly what the pipeline needs and nothing more.

Practical setup: week one

Here is how I recommend getting started.

Day 1-3: Default mode. Use Claude normally. Every time it asks for permission, notice your reaction. If you approve instantly without reading — that command belongs on the allowlist. If you pause and check — that command stays gated.

Day 4-5: Build your allowlist. Add the commands you always approve. Build, lint, test, type check, git reads. Switch to Auto mode. Notice the speed difference.

Day 6-7: Add one hook. Pick the one thing you never want to happen. Force push to main. Writes to .env. Deletion of the database migration folder. Write a hook that blocks it. Now you have a safety net that does not depend on Claude following instructions.

Permissions are infrastructure. Like tests, like CI, like type checking — you invest time in setting them up once, and they pay dividends every day. Spend the hour. Configure your setup. The friction you remove compounds over every session from that point forward.