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 *)matchesnpm run test unit,npm run test --verbose, and similar variations. The space preceding*enforces a word boundary –Bash(npm *)would matchnpm run buildbut notnpmx. ReadandEditrules follow.gitignoresyntax.Read(./.env.*)blocks.env.local,.env.production, and all other dotenv variants.Read(./secrets/**)recursively blocks everything under thesecretsdirectory.- Deny rules primarily block built-in tools, not Bash subprocesses. A
Read(./.env)deny rule blocks theReadtool but won't preventcat .envexecuted via Bash. For comprehensive protection, you must deny both (e.g., addBash(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:
- Managed settings: Admin-deployed and cannot be overridden.
- Command line arguments: Such as
--allowedTools,--disallowedTools. - Local project settings:
.claude/settings.local.json, typically gitignored. - Shared project settings:
.claude/settings.json, committed to version control. - 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.