CLAUDE LABJP
MCP — The July 28 MCP spec release candidate drops the Mcp-Session-Id header and goes stateless, so remote MCP servers no longer need sticky sessionsAPPS — The same release adds MCP Apps for server-rendered UI and a Tasks extension for long-running workMEMORY — The Python 0.116.0, TypeScript 0.110.0, and Go 1.56.0 SDKs now send agent-memory-2026-07-22 on every memory store callSPILL — Output from agent_toolset and MCP tools past 100K characters now spills to a file in the sandbox, with the model receiving a truncated preview it can expandBG — MCP tool calls running past two minutes move to the background automatically, keeping the session usable; tune it with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSRESUME — Typing /resume in the agent view opens a picker of past sessions and brings your pick back as a background sessionMCP — The July 28 MCP spec release candidate drops the Mcp-Session-Id header and goes stateless, so remote MCP servers no longer need sticky sessionsAPPS — The same release adds MCP Apps for server-rendered UI and a Tasks extension for long-running workMEMORY — The Python 0.116.0, TypeScript 0.110.0, and Go 1.56.0 SDKs now send agent-memory-2026-07-22 on every memory store callSPILL — Output from agent_toolset and MCP tools past 100K characters now spills to a file in the sandbox, with the model receiving a truncated preview it can expandBG — MCP tool calls running past two minutes move to the background automatically, keeping the session usable; tune it with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSRESUME — Typing /resume in the agent view opens a picker of past sessions and brings your pick back as a background session
Articles/Claude Code
Claude Code/2026-04-13Intermediate

Claude Code Tool Permissions: Custom Allow/Deny Policies

Learn how to control Claude Code tool permissions with allowedTools, disallowedTools, and settings.json. Includes project-specific permission patterns for frontend, backend, and read-only review scenarios.

Claude Code203tool permissionssecurity13settings.json2allowedToolsdevelopment environment

After using Claude Code for a while, most developers hit the same tension: work near production data and every rm call feels risky, but lock things down too much and the constant permission dialogs slow everything down.

Claude Code's permission system is more configurable than most people realize. You can define project-specific allow and deny lists, and commit them to your repo so the whole team uses the same settings from day one. Here's how the system works and the patterns I actually use.

Understanding Claude Code's Permission Model

Claude Code's operations fall into four categories:

  • File operations: Read, Write, Edit
  • Bash execution: Shell commands via Bash
  • Web/browser: URL fetching, browser control
  • MCP: Access to external MCP servers

By default, most operations require a permission prompt. You can skip all prompts with --dangerously-skip-permissions, but that's not appropriate anywhere near real data. The better approach is to define exactly what's allowed.

CLI Flags for One-Off Sessions

The quickest way to customize permissions is via command-line flags:

# Whitelist: only allow these tools
claude --allowedTools "Read,Write,Edit,Bash(git status),Bash(git diff)"
 
# Blacklist: allow everything except these
claude --disallowedTools "Bash(rm),Bash(sudo)"

The Bash(command) syntax lets you control individual shell commands. Allowing Bash(git status) and Bash(git diff) but nothing else gives you an effectively read-only session — Claude can inspect code but can't modify anything or run arbitrary commands.

Flags are useful for one-off sessions, but for regular development you want the settings persisted.

Project-Level settings.json

Create .claude/settings.json in your project root and Claude Code picks it up automatically when started from that directory:

// .claude/settings.json
{
  "permissions": {
    "allow": [
      "Read",
      "Write",
      "Edit",
      "Bash(git *)",
      "Bash(npm run *)",
      "Bash(npx *)",
      "Bash(echo *)",
      "Bash(cat *)",
      "Bash(ls *)",
      "Bash(grep *)",
      "Bash(find *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)",
      "Bash(curl *)",
      "Bash(wget *)"
    ]
  }
}

Wildcards work: Bash(git *) permits all git subcommands. Committing this file means anyone who clones the repo gets appropriate permissions without any setup.

Permission Patterns by Project Type

Frontend development — lock down network commands:

{
  "permissions": {
    "allow": [
      "Read", "Write", "Edit",
      "Bash(git *)", "Bash(npm *)", "Bash(npx *)",
      "Bash(node *)", "Bash(ts-node *)"
    ],
    "deny": [
      "Bash(curl *)", "Bash(wget *)",
      "Bash(ssh *)", "Bash(scp *)"
    ]
  }
}

Backend API development — protect database commands:

{
  "permissions": {
    "allow": [
      "Read", "Write", "Edit",
      "Bash(git *)", "Bash(go *)", "Bash(make *)",
      "Bash(docker-compose up *)", "Bash(docker-compose down)"
    ],
    "deny": [
      "Bash(psql -c DROP *)",
      "Bash(redis-cli FLUSHALL)",
      "Bash(rm -rf /var/lib/*)"
    ]
  }
}

Code review only — true read-only mode:

{
  "permissions": {
    "allow": [
      "Read",
      "Bash(git log *)", "Bash(git diff *)", "Bash(git status)",
      "Bash(cat *)", "Bash(ls *)", "Bash(grep *)"
    ],
    "deny": ["Write", "Edit", "Bash(*)"]
  }
}

Note the order in the last example: allow lists specific Bash commands, then deny blocks all remaining Bash. The allow list takes precedence over the deny list for explicitly listed commands.

Global Defaults via ~/.claude/settings.json

For operations you never want Claude Code to perform, put them in ~/.claude/settings.json:

// ~/.claude/settings.json
{
  "permissions": {
    "deny": [
      "Bash(rm -rf /)",
      "Bash(sudo rm -rf *)",
      "Bash(format *)",
      "Bash(dd if=*)"
    ]
  }
}

Priority order: project-level .claude/settings.json overrides user-level ~/.claude/settings.json, which overrides defaults. Deny rules from both levels are merged — if either file denies something, it's denied.

Checking Active Permissions

During a Claude Code session, you can inspect what's currently allowed:

/permissions

This slash command lists all currently allowed and denied tools. Useful for verifying your settings file loaded correctly.

Common Pitfalls

Wildcards are broad. Bash(git *) allows git commit -m "anything" including commit messages you might not have reviewed. If you need to prevent commits, explicitly deny Bash(git commit *).

Scripts bypass command-level deny rules. If Bash(deploy.sh) is allowed but Bash(rm -rf *) is denied, the script can still run rm -rf internally. Be conservative about which scripts you allow.

MCP permissions are separate. MCP server access is controlled through mcpServers in settings.json, not through the allowedTools Bash mechanism. If you're using MCP tools, review that section separately.

Taking 10 minutes to set up a proper settings.json for each project eliminates most of the vague anxiety that comes with using an AI agent near important files — and makes it easier to use Claude Code more aggressively where it's actually safe to do so.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-07-19
A Committed Symlink That Points Outside the Worktree — Auditing Repos Before You Let AI Spin Up Parallel Trees
Claude Code 2.1.212 fixed a bug where a committed symlink under .claude/worktrees could be followed during worktree creation and write outside the repo. The patch closes the following side. Here is an audit script for the committed side, plus a quarantine workflow.
Claude Code2026-07-16
The Permission Rules You Added for Safety Are Taxing Every Turn — Auditing the Ruleset Without Loosening It
Version 2.1.209 fixed the per-turn slowdown from large deny/ask rulesets, but the design debt in your rules is still yours. Here are the audit scripts, a shadowing detector, a turn-timing harness, and how to fold enumerated rules into prefix rules safely.
Claude Code2026-07-25
Locking down Claude Code sandbox egress with strictAllowlist
Using sandbox.network.strictAllowlist in Claude Code v2.1.219 to tighten an automation pipeline's outbound traffic to a minimal allowlist, with a runnable script to discover real destinations and the pitfalls I hit.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →