Permissions & Enterprise Deployment: Securing and Scaling Claude Code#

Executive Summary#

Claude Code’s permission system controls what actions Claude can take – from file reads to shell commands to network access. Permissions cascade across five settings scopes (managed, CLI, local, project, user), with deny rules always winning. Enterprise deployments add managed policy files, sandbox enforcement, provider flexibility (Bedrock, Vertex AI, Foundry), and organization-wide controls. This article covers the full permission lifecycle from individual developer configuration to team-wide lockdown.

ScopeLocationWho ControlsCan Override?
ManagedSystem-level path (admin-only)IT/AdminNo (highest)
CLICommand-line argumentsDeveloperOnly managed
Local.claude/settings.local.jsonDeveloperManaged + CLI
Project.claude/settings.jsonTeamAbove scopes
User~/.claude/settings.jsonDeveloperAll above

Table of Contents#

Permission Modes#

Permission modes set the overall behavior for how Claude handles tool approval:

ModeBehavior
defaultPrompts for permission on first use of each tool
acceptEditsAuto-accepts file edit/write operations for the session
planRead-only: Claude can analyze but not modify or execute
autoRuns without routine prompts; a safety classifier approves or blocks each action
dontAskAuto-denies unless pre-approved via rules
bypassPermissionsSkips all permission prompts (isolated environments only)

Set via settings:

{
  "permissions": {
    "defaultMode": "acceptEdits"
  }
}

Or via CLI: claude --permission-mode acceptEdits

bypassPermissions warning: This mode gives Claude unrestricted access to your filesystem, shell, and network. Only use in isolated environments (containers, VMs, CI runners). Enterprise admins can prevent this mode entirely with disableBypassPermissionsMode.

Auto Mode#

auto mode (research preview, v2.1.83+) lets Claude run without routine permission prompts while a separate classifier model reviews each action first. Reads and file edits inside the working directory are auto-approved; everything else goes to the classifier, which blocks actions that escalate beyond your request, target unrecognized infrastructure, or look driven by hostile content Claude read. Explicit ask rules still force a prompt. Unlike bypassPermissions, auto mode keeps a safety check in the loop – it reduces prompts rather than removing oversight.

Auto mode is not a silent default. It appears in the Shift+Tab cycle only when your account meets every requirement, and cycling to it shows a one-time opt-in prompt:

RequirementDetail
PlanAll plans. On Team and Enterprise, an admin must enable it in Claude Code admin settings first
ModelOpus 4.6+ or Sonnet 4.6 on the Anthropic API; Opus 4.7 or 4.8 on Bedrock, Vertex AI, and Foundry
ProviderDefault-available on the Anthropic API; off on Bedrock, Vertex AI, and Foundry until CLAUDE_CODE_ENABLE_AUTO_MODE=1 (v2.1.158+)

Set defaultMode: "auto" only in user (~/.claude/settings.json) or managed settings; Claude Code ignores auto from project or local settings (since v2.1.142) so a repository cannot grant itself auto mode.

What the classifier does by default:

AllowedBlocked
Local file operations in the working directorycurl | bash and other download-and-execute patterns
Installing dependencies from lock files/manifestsSending sensitive data to external endpoints
Read-only HTTP requestsProduction deploys and migrations, mass cloud-storage deletes
Pushing to the branch you started on or Claude madeForce push, pushing directly to main, granting IAM/repo access

On entering auto mode, broad code-execution allow rules (Bash(*), wildcarded interpreters like Bash(python*), package-manager run commands, Agent allow rules) are dropped and restored when you leave; narrow rules like Bash(npm test) carry over. Boundaries you state in conversation (“don’t push until I review”) become block signals, but they are re-read from the transcript each check and can be lost to compaction – use a deny rule for a hard guarantee. If the classifier blocks 3 actions in a row or 20 total, auto mode pauses and Claude Code resumes prompting; under -p it aborts instead.

Tuning trusted infrastructure. Out of the box the classifier trusts only the working directory and the repo’s remotes. Tell it what else is internal with the autoMode settings block (read from user, local, or managed settings – never shared project settings). autoMode.environment is the field most teams need; allow, soft_deny, and hard_deny override the built-in rule lists. Include the literal "$defaults" to extend a list instead of replacing it – omitting it discards every built-in rule for that section, including the data-exfiltration and force-push blocks.

{
  "autoMode": {
    "environment": [
      "$defaults",
      "Source control: github.acme.com/acme-corp and all repos under it",
      "Trusted buckets: s3://acme-build-artifacts"
    ],
    "soft_deny": [
      "$defaults",
      "Never run migrations outside the migrations CLI"
    ],
    "hard_deny": [
      "$defaults",
      "Never send repository contents to third-party code-review APIs"
    ]
  }
}

Precedence inside the classifier: hard_deny (unconditional) > soft_deny (clearable by explicit user intent or an allow exception) > allow > stated user intent. Inspect rules with claude auto-mode defaults, claude auto-mode config, and claude auto-mode critique. Admins lock the mode off with the managed disableAutoMode setting (set to "disable"); permissions.deny rules still run before the classifier and cannot be overridden by any autoMode entry.

Permission Rules#

Allow, Deny, Ask#

Permission rules are lists of tool patterns that control whether actions are approved, blocked, or require explicit confirmation:

{
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(git diff *)",
      "Bash(git status)"
    ],
    "ask": ["Bash(git push *)", "Bash(git commit *)"],
    "deny": ["Bash(curl *)", "Bash(wget *)", "Read(./.env)", "Read(./.env.*)"]
  }
}
  • allow: Auto-approved without prompting
  • ask: Always prompts the user, even if previously approved
  • deny: Always blocked, no prompt shown

Rule Evaluation Order#

Rules are evaluated: deny -> ask -> allow. First matching rule wins.

Tool call: Bash("npm run test")
  1. Check deny rules → no match
  2. Check ask rules → no match
  3. Check allow rules → matches "Bash(npm run test *)" → ALLOW

If no rule matches, the permission mode determines behavior:

  • default / acceptEdits: Prompt the user
  • dontAsk: Auto-deny
  • bypassPermissions: Auto-allow

Tool-Specific Syntax#

Bash – glob patterns with *:

PatternMatches
Bash(npm run build)Exact command
Bash(npm run *)Any npm run subcommand
Bash(git * main)Any git command targeting main
Bash(* --version)Any command with --version

Claude Code is aware of shell operators. Bash(safe-cmd *) will not permit safe-cmd && malicious-cmd. The matching is semantically aware of shell syntax, not string-based.

Read and Edit – gitignore-style path patterns:

PatternMatches
Read(./.env)The .env file in project root
Read(./.env.*)All .env.local, .env.production, etc.
Read(//Users/alice/secrets/**)Absolute path (note double /)
Read(~/Documents/*.pdf)Home-relative path
Edit(/src/**/*.ts)All TypeScript files recursively in src/

Single * matches within a directory. Double ** matches recursively.

WebFetch:

PatternMatches
WebFetch(domain:example.com)Fetch requests to example.com

MCP tools:

PatternMatches
mcp__puppeteerAny tool from puppeteer server
mcp__puppeteer__puppeteer_navigateSpecific tool
mcp__puppeteer__*All tools from puppeteer (glob)

Task (subagents):

PatternMatches
Task(Explore)The Explore subagent
Task(my-custom-agent)A custom subagent

Skill:

PatternMatches
Skill(commit)The commit skill
Skill(deploy *)Any skill with “deploy” prefix

Tool-name globs: Deny and ask rules accept a glob in the tool-name position. "*" matches every tool and "mcp__*" matches every MCP tool. A tool matched by a bare-name glob deny rule is removed from Claude’s context entirely, the same as a bare tool name; a scoped rule like Bash(rm *) instead leaves the tool available and blocks matching calls. Allow rules accept tool-name globs only after a literal mcp__<server>__ prefix (for example mcp__github__get_*); an unanchored allow glob like "*" is skipped with a warning and approves nothing. A deny or ask rule whose tool name matches no known tool warns at startup to catch typos, except names containing _ or *.

Permission Prompts and UX#

Tool TypeExamplePrompt Required?“Don’t ask again” Duration
Read-onlyFile reads, GrepNoN/A
Bash commandsShell executionYes (first time)Permanent (per project + command)
File modificationEdit, WriteYes (first time)Until session end

Additional behaviors:

  • Command injection detection: suspicious commands always require manual approval, even if allowlisted
  • Fail-closed matching: unmatched commands default to requiring approval
  • /permissions slash command shows all active rules and their source

Settings Cascade#

Scope Precedence#

Settings from higher-precedence scopes override lower ones:

PrecedenceScopeLocationControlled By
1 (highest)ManagedSystem-level pathIT Admin
2CLICommand-line argumentsDeveloper
3Local.claude/settings.local.jsonDeveloper
4Project.claude/settings.jsonTeam (git)
5 (lowest)User~/.claude/settings.jsonDeveloper

If a permission is allowed in user settings but denied in project settings, it’s denied. Managed settings override everything.

Settings File Locations#

User settings: ~/.claude/settings.json

Project settings: .claude/settings.json (committed to git – shared with team)

Local settings: .claude/settings.local.json (gitignored – personal to you)

Managed settings:

  • macOS: /Library/Application Support/ClaudeCode/managed-settings.json
  • Linux/WSL: /etc/claude-code/managed-settings.json
  • Windows: C:\Program Files\ClaudeCode\managed-settings.json

All files use the same JSON format.

Sandboxing#

OS-Level Enforcement#

Claude Code can sandbox Bash commands at the operating system level:

OSTechnologySetup
macOSSeatbelt frameworkBuilt-in, no setup
Linuxbubblewrap + socatInstall required

Enable via /sandbox command or settings.

Filesystem Isolation#

  • Default writes: Current working directory and subdirectories only
  • Default reads: Entire system except explicitly denied directories
  • Configurable: Additional allowed/denied paths in sandbox config

Network Isolation#

  • Domain-based restrictions via a proxy server
  • allowedDomains controls which domains Bash commands can reach
  • Unix socket access via allowUnixSockets
  • Local port binding via allowLocalBinding

Sandbox Configuration#

{
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "excludedCommands": ["git", "docker"],
    "allowUnsandboxedCommands": false,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org", "registry.npmjs.org"],
      "allowUnixSockets": [],
      "allowLocalBinding": true,
      "httpProxyPort": 8080,
      "socksProxyPort": 8081
    }
  }
}
FieldDescription
enabledTurn sandboxing on/off
autoAllowBashIfSandboxedAuto-approve Bash when sandboxed
excludedCommandsCommands that bypass the sandbox (e.g., git)
allowUnsandboxedCommandsWhether non-sandboxed commands can run at all
allowedDomainsDomain allowlist for network access
allowUnixSocketsSpecific Unix sockets that can be accessed
allowLocalBindingWhether Bash can bind local ports

On Linux/WSL the sandbox relies on the bubblewrap and socat binaries. If they live outside the default search path, set the managed-only sandbox.bwrapPath and sandbox.socatPath settings to their locations.

Security Limitations#

  • Network filtering is domain-based, not traffic-inspecting – data exfiltration to allowed domains is possible
  • Domain fronting can bypass domain restrictions
  • allowUnixSockets for the Docker socket effectively grants host-level access
  • Overly broad filesystem write permissions can enable privilege escalation
  • enableWeakerNestedSandbox considerably weakens security (Docker use only)

Enterprise Managed Settings#

Managed Settings File#

Managed settings require administrator privileges to create or modify. They use the same JSON format as regular settings but cannot be overridden by any other scope.

Locations:

  • macOS: /Library/Application Support/ClaudeCode/managed-settings.json
  • Linux/WSL: /etc/claude-code/managed-settings.json
  • Windows: C:\Program Files\ClaudeCode\managed-settings.json

Managed-Only Controls#

These settings are only available in managed settings files:

SettingDescription
disableBypassPermissionsModeSet to "disable" to prevent --dangerously-skip-permissions
disableAutoModeSet to "disable" to prevent users from enabling auto mode
requiredMinimumVersionRefuse to start if the running version is older than this; claude update/install/doctor still work
requiredMaximumVersionRefuse to start if the running version is newer than this; auto-updates skip versions above it, and claude update/install/doctor still work
allowManagedPermissionRulesOnlyBlocks user/project permission rules; only managed rules apply
allowManagedHooksOnlyBlocks user, project, and plugin hooks; only managed and SDK hooks allowed
parentSettingsBehavior"first-wins" (default) or "merge": whether host-supplied settings (Agent SDK, IDE) apply under the admin tier (tighten-only)
strictKnownMarketplacesControls which plugin marketplaces users can add
pluginSuggestionMarketplacesAllowlist of org marketplaces whose plugins may surface as contextual install suggestions
allowedMcpServersAllowlist of MCP servers users can configure
deniedMcpServersDenylist of MCP servers (takes precedence over allow)
allowAllClaudeAiMcpsLoad claude.ai cloud MCP connectors alongside a deployed managed-mcp.json
forceLoginMethodRestrict auth to claudeai or console only
forceLoginOrgUUIDAuto-select organization during login

Example Enterprise Configuration#

{
  "permissions": {
    "allow": [
      "Bash(git diff *)",
      "Bash(git status)",
      "Bash(npm run *)",
      "Bash(go test *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(~/.*)",
      "Bash(curl *)",
      "Bash(wget *)",
      "WebFetch"
    ],
    "defaultMode": "default"
  },
  "allowManagedHooksOnly": true,
  "allowManagedPermissionRulesOnly": true,
  "disableBypassPermissionsMode": "disable",
  "allowedMcpServers": [{ "serverName": "github" }, { "serverName": "gitlab" }],
  "deniedMcpServers": [{ "serverName": "filesystem" }],
  "sandbox": {
    "enabled": true,
    "excludedCommands": ["git"],
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    }
  },
  "companyAnnouncements": [
    "Review code guidelines at docs.acme.com before committing"
  ]
}

Company Announcements#

Organization-wide messaging displayed to all users:

{
  "companyAnnouncements": [
    "Welcome to ACME Corp! Review our code guidelines at docs.acme.com",
    "Reminder: All PRs require code review before merge"
  ]
}

API Provider Configuration#

Anthropic (Default)#

Default provider. Authenticate via claude login or set ANTHROPIC_API_KEY.

AWS Bedrock#

export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1

Authentication options:

  • AWS CLI: aws configure
  • Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
  • SSO profile: AWS_PROFILE
  • Bedrock API keys: AWS_BEARER_TOKEN_BEDROCK

Auto-refresh credentials:

{
  "awsAuthRefresh": "aws sso login --profile myprofile",
  "env": { "AWS_PROFILE": "myprofile" }
}

Default models:

  • Primary: global.anthropic.claude-sonnet-4-5-20250929-v1:0
  • Fast/small: us.anthropic.claude-haiku-4-5-20251001-v1:0

Override small model region: ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION=us-west-2

IAM permissions: bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream, bedrock:ListInferenceProfiles

Guardrails integration:

{
  "env": {
    "ANTHROPIC_CUSTOM_HEADERS": "X-Amzn-Bedrock-GuardrailIdentifier: your-guardrail-id\nX-Amzn-Bedrock-GuardrailVersion: 1"
  }
}

Google Vertex AI#

export CLAUDE_CODE_USE_VERTEX=1
export CLOUD_ML_REGION=global
export ANTHROPIC_VERTEX_PROJECT_ID=YOUR-PROJECT-ID

Authentication: gcloud auth application-default login

Default models:

  • Primary: claude-sonnet-4-5@20250929
  • Fast/small: claude-haiku-4-5@20251001

Per-model region overrides:

export VERTEX_REGION_CLAUDE_3_5_HAIKU=us-east5
export VERTEX_REGION_CLAUDE_4_0_OPUS=europe-west1

IAM: roles/aiplatform.user or custom with aiplatform.endpoints.predict

Microsoft Foundry#

export CLAUDE_CODE_USE_FOUNDRY=1
export ANTHROPIC_FOUNDRY_RESOURCE={resource}
export ANTHROPIC_DEFAULT_SONNET_MODEL='claude-sonnet-4-5'
export ANTHROPIC_DEFAULT_HAIKU_MODEL='claude-haiku-4-5'
export ANTHROPIC_DEFAULT_OPUS_MODEL='claude-opus-4-6'

Authentication: API key (ANTHROPIC_FOUNDRY_API_KEY) or Microsoft Entra ID.

RBAC: Azure AI User or Cognitive Services User roles.

LLM Gateway Support#

For corporate gateways or proxies, override base URLs:

VariableProvider
ANTHROPIC_BASE_URLAnthropic
ANTHROPIC_BEDROCK_BASE_URLBedrock
ANTHROPIC_VERTEX_BASE_URLVertex AI

Skip provider auth (for gateways that handle authentication):

VariableProvider
CLAUDE_CODE_SKIP_BEDROCK_AUTHBedrock
CLAUDE_CODE_SKIP_VERTEX_AUTHVertex AI
CLAUDE_CODE_SKIP_FOUNDRY_AUTHFoundry

Authentication Helpers#

For dynamic credential rotation:

{
  "apiKeyHelper": "vault read -field=api_key secret/claude"
}

The helper script is called after 5 minutes or on HTTP 401. Customize the interval with CLAUDE_CODE_API_KEY_HELPER_TTL_MS.

CI/CD Permission Strategies#

Headless Mode Permissions#

In headless mode (claude -p), configure permissions through flags:

# Allowlist specific tools
claude -p "Fix the bug" --allowedTools "Read,Edit,Bash(npm test *)"

# Remove tools entirely from model context
claude -p "Review code" --disallowedTools "Bash,Write"

# Budget cap
claude -p "Refactor module" --max-budget-usd 5.00

# Turn limit
claude -p "Fix this" --max-turns 3

Key notes:

  • PermissionRequest hooks do NOT fire in headless mode – use PreToolUse hooks instead
  • Trust verification is disabled in headless mode
  • --permission-prompt-tool specifies an MCP tool to handle permission prompts programmatically

GitHub Actions#

- uses: anthropics/claude-code-action@v1
  with:
    allowed_tools: "Bash(npm run *),Read,Edit"
    disallowed_tools: "WebFetch"
    max_budget_usd: "10.00"
    max_turns: "50"

Security Hardening#

Minimal-Interruption Developer Setup#

For developers who want flow with reasonable guardrails:

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(bun run *)",
      "Bash(go test *)",
      "Bash(git diff *)",
      "Bash(git status)",
      "Bash(git log *)",
      "Bash(git add *)",
      "Bash(* --version)",
      "Bash(* --help *)"
    ],
    "deny": ["Bash(curl *)", "Bash(wget *)", "Read(./.env)", "Read(./.env.*)"],
    "defaultMode": "acceptEdits"
  }
}

Strict Lockdown Setup#

For environments requiring maximum control:

{
  "permissions": {
    "allow": ["Bash(git diff *)", "Bash(git status)"],
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "WebFetch"
    ],
    "defaultMode": "dontAsk"
  },
  "disableAllHooks": true,
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com"]
    }
  }
}

In dontAsk mode, only explicitly allowed tools work. Everything else is auto-denied without prompting.

Hooks for Security Enforcement#

Use PreToolUse hooks for runtime permission decisions beyond what static rules can express:

#!/usr/bin/env bash
# Block commands that might expose secrets
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -qiE 'password=|token=|secret=|api_key='; then
  echo "Blocked: potential credential exposure" >&2
  exit 2
fi

exit 0

See the Custom Hooks Cookbook for complete security hook recipes.

Debugging Permissions#

  • /permissions – lists all active rules and which settings file they come from
  • /status – shows authentication, proxy, URL settings, and provider configuration
  • claude --debug – full permission evaluation output
  • ANTHROPIC_LOG=debug – logs API requests for provider debugging
  • Ctrl+O – toggles verbose mode to see hook output

Best Practices#

  1. Use project settings for team rules. Put .claude/settings.json in version control. This ensures everyone on the team has the same permission rules and deny lists.

  2. Deny before you allow. Start with deny rules for dangerous patterns (curl, wget, .env access), then selectively allow safe operations. Deny rules always win.

  3. Use acceptEdits for daily work. File edit prompts add friction without much safety benefit for experienced developers. Auto-accept edits and focus deny rules on truly dangerous operations.

  4. Use dontAsk for strict environments. In dontAsk mode, only explicitly allowed tools work. Everything else is silently denied. This is the safest mode for environments where you want predictable behavior.

  5. Enable sandboxing for untrusted code. When working with unfamiliar codebases or running untrusted scripts, the sandbox limits blast radius at the OS level.

  6. Scope permissions appropriately. Personal preferences go in ~/.claude/settings.json. Team rules go in .claude/settings.json. Sensitive local overrides go in .claude/settings.local.json (gitignored).

  7. Use managed settings for enterprise. Deploy managed-settings.json via Mobile Device Management (MDM) or configuration management. Set allowManagedPermissionRulesOnly to prevent teams from weakening rules.

  8. Pre-approve CI/CD tools. In headless mode, use --allowedTools to explicitly list what Claude can use. Don’t rely on --dangerously-skip-permissions in production CI.

  9. Test provider configuration with /status. Before deploying Bedrock/Vertex/Foundry configuration to a team, verify auth and model routing works.

  10. Use authentication helpers for credential rotation. The apiKeyHelper setting supports dynamic credential refresh, which is essential for enterprise environments with rotating secrets.

Anti-Patterns#

  1. Using --dangerously-skip-permissions on developer machines. This flag gives Claude unrestricted access. It’s designed for isolated containers, not laptops with your SSH keys and cloud credentials.

  2. Overly broad allow rules. Bash(*) allows any shell command. Be specific: Bash(npm run *), Bash(git diff *).

  3. No deny rules for sensitive files. At minimum, deny Read access to .env files and SSH keys.

  4. Relying solely on hooks for security. Hooks are defense-in-depth. Use permission rules as the primary control and hooks for runtime validation.

  5. Not committing project settings. If .claude/settings.json isn’t in git, every developer has different rules. Inconsistency leads to security gaps.

  6. Hardcoding API keys in settings files. Use environment variables or apiKeyHelper scripts. Never commit secrets to settings files.

  7. Disabling all hooks in enterprise. disableAllHooks: true removes useful automation. Use allowManagedHooksOnly: true instead to keep managed hooks while blocking user-defined ones.

  8. Ignoring sandbox limitations. Sandboxing is not bulletproof. Allowed domains can be used for data exfiltration. Docker socket access grants host-level privileges. Layer defenses.

References#