Agentic AI Guide

Claude Code permissions: a tested setup to lock down your agent

We tested Claude Code deny rules and the sandbox in a throwaway repo. The settings file that kept our agent out of .env, ~/.ssh and our monitoring code.

To keep Claude Code away from your secrets and the code that watches it, you need two things: deny rules in a .claude/settings.json file, and the sandbox switched on. Neither is on by default.

Table of our test results: Claude Code was blocked from reading .env with its Read tool, with cat and from a Python script, from editing or appending to monitoring/, from reading ~/.ssh and from writing in the home folder, and git push needed approval
Graphic: Mavday. Source: Mavday test log, 22 October 2026

We set up both in a throwaway repo on 22 October and told Claude Code to get past them. It couldn’t. Here is the exact file, what each line does, and the gaps you still have to cover yourself.

What you need first

  • Claude Code. We tested version 2.1.306. Run claude --version to check yours.
  • macOS, Linux or WSL2 for the sandbox. The docs say: “On native Windows, Claude Code runs commands unsandboxed.” On Linux and WSL2, install bubblewrap and socat first.
  • A repo you can commit a settings file to. The file goes in .claude/settings.json at the root.

Check which permission mode you’re in

Recent versions start in auto mode. From the docs: “With Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions.” Auto mode runs actions without asking, with background safety checks.

Table of Claude Code permission modes: Manual runs reads only; acceptEdits runs reads, file edits and common file commands; plan runs reads plus commands the safety checker approves when auto mode is available; auto runs everything with background safety checks; dontAsk runs reads in your working folders and pre-approved tools and denies anything that would prompt; bypassPermissions runs everything and is for isolated containers and VMs only

What each permission mode lets Claude Code do without asking. Deny rules apply in every mode. Graphic: Mavday. Source: Claude Code docs, permission modes

Press Shift+Tab to cycle modes. The good news is that your rules hold whatever mode you pick. The docs say: “Deny rules block in every mode, including bypassPermissions.” That’s why rules matter more than modes.

Write deny rules for your secrets

Create .claude/settings.json with this:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "deny": ["Read(.env)", "Read(.env.*)", "Read(~/.ssh/**)", "Edit(monitoring/**)"],
    "ask": ["Bash(git push *)"]
  },
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "filesystem": { "denyWrite": ["./monitoring"] }
  }
}

The path rules use gitignore-style patterns, and the prefix matters:

  • Read(.env) with no prefix matches a .env at any depth under the current directory. The docs: “Bare filenames follow gitignore semantics and match at any depth”. In our test, Read(.env.*) also blocked config/.env.local.
  • Read(./.env), in the docs’ words, “Matches reading the .env file in the current directory”.
  • ~/ is your home folder, so Read(~/.ssh/**) covers everything in .ssh.
  • A single leading / is relative to the settings source (for a project file, the folder you start Claude Code in), not the root of your disk. Use // for a true absolute path.

A Read deny also blocks Claude’s Edit and Write tools on the same path. Settings files are strict JSON: a comment or a trailing comma is a syntax error.

Protect the code that watches your agent

Edit(monitoring/**) stops Claude Code editing your monitoring folder. Because it has no prefix, it also matched tests/monitoring/ in our test. Swap in the folders that hold your alerting, audit logging or security checks, and their tests.

Use Edit, not Write, for path rules. The docs warn that for a Write path rule, Claude Code “accepts the rule but never consults it”. This is exactly the kind of change that went wrong in OpenAI’s two agent incidents, where agents weakened monitoring in the code they were editing.

Turn on the sandbox

Deny rules have limits, and the docs are clear about them. They cover Claude’s own file tools, and recognised shell commands such as cat, sed and tee. They don’t cover “a Python or Node script that opens files itself”.

Abstract illustration of a glowing node inside a box of thin light, with blocks outside behind arched barriers

An illustration of an agent working inside a boundary, with protected things outside it. Not a picture of Claude Code. Illustration: Mavday, AI-generated

That’s what the sandbox is for. It’s an operating-system-level fence around the shell commands Claude runs, and “The sandbox is off by default.” The sandbox block in our file does three things:

  • "enabled": true turns it on. You can also run /sandbox in a session.
  • "allowUnsandboxedCommands": false stops Claude Code retrying a blocked command outside the sandbox.
  • "denyWrite": ["./monitoring"] blocks shell writes into the monitoring folder. Your Read and Edit deny rules are merged into the sandbox too.

One warning worth acting on: if the sandbox can’t start, Claude Code runs commands without it by default. If you’d rather it refused, the docs’ sandbox.failIfUnavailable setting makes Claude Code exit at startup instead. We didn’t test that setting.

Make risky commands ask first

"ask": ["Bash(git push *)"] makes Claude Code ask before a git push. Ask rules still prompt in auto mode.

Treat Bash rules as a seatbelt, not a lock. The docs say a Bash rule “isn’t a security boundary around the program”, and give an example. Bash(git push *) doesn’t catch git -C . push origin main. For anything that must never happen, rely on the sandbox or a hook, not on matching command text.

Check what loaded

Before you trust the file, check it took effect:

  • /permissions lists every rule and the file it came from.
  • /status shows which settings files loaded.
  • claude doctor lists any entries Claude Code rejected.
  • /sandbox shows the sandbox’s configuration.

What happened when we tested it

We ran Claude Code 2.1.306 on macOS in a throwaway repo, with fake secrets. Each test was a single non-interactive run (claude -p) in acceptEdits mode, so the agent could edit files without asking.

We made ten attempts to read the secrets, edit the monitoring code or write outside the project. All ten were blocked. The git push was refused too: in a non-interactive run, an ask rule can’t show its prompt, so the command doesn’t run.

Then we removed the settings file and repeated two tests. Claude Code read .env and edited the monitoring file.

The messages you’ll see:

  • From a deny rule: File is in a directory that is denied by your permission settings.
  • From the sandbox: Operation not permitted.

One detail surprised us. On its first try, without being told .env was a test file, the model declined to open it from a Python script. With that context, it tried, and the sandbox stopped it. Don’t rely on the model’s judgement: the settings are what held every time.

What goes wrong

  • The sandbox fails open. On an unsupported platform, or without its dependencies, commands run unsandboxed unless you set failIfUnavailable.
  • Write path rules do nothing. Use Edit for paths.
  • Bash rules miss rewritten commands. Back them with the sandbox.
  • bypassPermissions is for throwaway machines. Deny rules still apply, but the docs say to use it only “in isolated environments like containers, VMs, or dev containers without internet access”.

Going further: production codebases

This setup protects one developer’s machine. On a production codebase, especially in a regulated business, treat it as the first layer of several. The docs are explicit that CLAUDE.md instructions aren’t enforcement: “Permission rules are enforced by Claude Code, not by the model.” So stack layers the agent can’t talk its way past:

  • Read-only agents. For review or investigation work, the dontAsk mode denies anything that would prompt. The docs pitch it for “Locked-down CI and scripts”.
  • Network. The sandbox starts with no allowed domains. Add only the ones you need: the docs warn that “Allowing broad domains such as github.com can create paths for data exfiltration.”
  • Credentials. “There is no built-in credential deny list”, so list the files and environment variables to block. Better still, keep production credentials off the machine entirely.
  • Tools outside the sandbox. Local MCP servers and hooks “run with your full access”. Deny MCP tools you don’t use, with rules like mcp__*.
  • Rules nobody can switch off. For a team, put the rules in managed settings, which no other level “including command line arguments” can override. They can also disable bypassPermissions. For hard blocks on commands, add a PreToolUse hook: exiting with code 2 stops the action even if an allow rule would let it through.
  • Git, enforced on the server. Branch protection, required reviews and a bot account limited to opening pull requests hold even when a local rule is bypassed.

For how other developers set up their agents, see how leading developers code with AI agents.

Get the builder’s briefing

Twice a week: the AI news worth knowing, from new models and agents to the money behind them. No hype, no spam.

We’ll email you to confirm before the first issue. Unsubscribe any time. See our privacy policy.

Sources

6
  1. Configure permissions · Claude Code docs
  2. Settings files and precedence · Claude Code docs
  3. Configure the sandboxed Bash tool · Claude Code docs
  4. Choose a permission mode · Claude Code docs
  5. Hooks reference · Claude Code docs
  6. Deploy managed settings · Claude Code docs

Mr B

Co-founder & AI Engineer

Mr B is a co-founder of Mavday and an AI engineer, closely involved in agentic engineering: building software with AI agents. He writes about how that works in practice, and about the wider AI industry.

Work with us on a tutorial for your product

Related reading

See all

Latest