Labs

Securing Claude Code: 5 Permission Patterns for Robust AI Agent Control

Securing Claude Code: 5 Permission Patterns for Robust AI Agent Control

Claude Code ships with a tiered permission system that many developers rarely configure beyond a quick "Yes, don't ask again." This default approach often creates invisible security gaps. Every auto-approved command persists permanently in project settings, and every unconfigured tool runs with maximum access. The result is an AI assistant potentially having more filesystem and network access than any human on your development team.

This article outlines 5 crucial permission patterns to properly lock down Claude Code, ranging from basic deny rules to OS-level sandboxing, ensuring robust security for your projects.

Pattern 1: Deny-First Rules in settings.json

Claude Code evaluates permission rules in a strict order: deny, then ask, then allow. Rules are checked by category: all deny rules first, then ask rules, then allow rules. A deny rule always takes precedence over an allow rule, irrespective of its position in the JSON array or the settings file it resides in.

Here's a starter configuration to block secrets, restrict network tools, and permit only your build commands:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(git push --force *)",
      "Bash(rm -rf *)"
    ],
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(git commit *)",
      "Bash(python -m pytest *)",
      "Bash(ruff check *)"
    ]
  }
}

Save this configuration to .claude/settings.json in your project root. Committing this file to version control ensures that all developers on your team inherit the same security restrictions.

Three key aspects to note:

  • Glob patterns utilize * for wildcards. For instance, Bash(npm run test *) matches npm run test unit, npm run test --verbose, and similar variations. The space preceding * enforces a word boundary – Bash(npm *) would match npm run build but not npmx.
  • Read and Edit rules follow .gitignore syntax. Read(./.env.*) blocks .env.local, .env.production, and all other dotenv variants. Read(./secrets/**) recursively blocks everything under the secrets directory.
  • Deny rules primarily block built-in tools, not Bash subprocesses. A Read(./.env) deny rule blocks the Read tool but won't prevent cat .env executed via Bash. For comprehensive protection, you must deny both (e.g., add Bash(cat .env)) or enable sandboxing (Pattern 4).

Pattern 2: The 4-Layer Settings Hierarchy

Claude Code loads settings from four distinct sources, evaluated in the following precedence order:

  1. Managed settings: Admin-deployed and cannot be overridden.
  2. Command line arguments: Such as --allowedTools, --disallowedTools.
  3. Local project settings: .claude/settings.local.json, typically gitignored.
  4. Shared project settings: .claude/settings.json, committed to version control.
  5. User settings: ~/.claude/settings.json, applied globally.

If a tool is denied at a higher precedence layer, it remains denied, even if an allow rule exists in a lower-precedence settings file.

↗ Read original source