Claude Code security: a rollout guide for security and platform teams
Permissions, managed settings, sandboxing, hooks and MCP approval for Claude Code, plus the gaps a team has to close itself.
For Security and platform teams
Claude Code security comes down to four controls that you configure yourself: permission rules and modes, managed settings that developers cannot override, the OS-level sandbox for shell commands, and an allowlist for MCP servers. Permission rules are enforced by the tool and not by the model, so they hold even when the model is talked into something. Most of a rollout is deciding which of these to lock.
This guide follows the official documentation as of late October 2026. Claude Code ships changes often, so the version numbers below are there to help you check your own fleet.
How do Claude Code permissions work?
Every tool call goes through a permission check. In Manual mode (the default mode in config), reads inside the working directory run without asking. Shell commands, file edits, web fetches and web searches ask first, apart from a built-in set of read-only commands such as ls, cat and git status.
You change that behavior with rules in three lists: allow, ask and deny. They are evaluated in a fixed order: deny first, then ask, then allow. The first match wins, and specificity does not matter. A broad Bash(aws *) deny beats a narrow Bash(aws s3 ls) allow, and an allow rule can never carve an exception out of a deny.
A rule is either a bare tool name or a tool with a specifier:
Bash(npm run build)matches that exact commandRead(./.env)matches reads of.envin the current directorymcp__puppeteer__*matches every tool from thepuppeteerMCP serverWebFetch(domain:example.com)matches fetches to one domain
Know the limit of command rules. A Bash rule matches the command text Claude writes. Bash(curl *) stops curl https://example.com but not /usr/bin/curl https://example.com or sh -c 'curl https://example.com'. Treat Bash deny rules as guardrails against what Claude usually produces, and put the real boundary in the sandbox.
Read the full permissions reference once before you write policy. The wildcard and path-anchoring rules are specific enough to surprise you.
Which permission mode should a team allow?
Claude Code has six modes: default (Manual), acceptEdits, plan, auto, dontAsk and bypassPermissions.
Two details matter for a rollout. First, since version 2.1.283, auto mode is the built-in starting mode for interactive terminal and VS Code sessions. In auto mode a separate classifier model reviews actions instead of the developer. It blocks things like curl | bash, force pushes, production deploys, sending sensitive data to external endpoints and printing a live credential. The documentation is blunt that auto mode reduces prompts but does not guarantee safety. Your own ask and deny rules still apply on top of it.
Second, bypassPermissions skips prompts entirely, including writes to .git and .claude. The documentation limits it to containers and VMs where Claude Code cannot do damage. dontAsk auto-denies anything that would prompt, which makes it a fit for CI.
You can remove both from a developer's reach with two managed keys:
permissions.disableBypassPermissionsModeset to"disable"permissions.disableAutoModeset to"disable", if you do not want classifier-approved autonomy at all
One trap: defaultMode: "auto" has no effect in a project's .claude/settings.json or .claude/settings.local.json. Set it in ~/.claude/settings.json or in managed settings.
Where do Claude Code settings live, and which one wins?
Settings come from five levels, and precedence runs from highest to lowest:
- Managed settings (
managed-settings.json, an MDM policy, or server-managed settings from the claude.ai console) - Command line (
--settings) - Project local (
.claude/settings.local.json) - Shared project (
.claude/settings.json) - User (
~/.claude/settings.json)
List keys such as permissions.allow merge across files instead of overriding each other. Deny rules behave differently from everything else: if any level denies a tool, no other level can allow it, and that includes --allowedTools.
Repository content deserves its own paragraph, because it is attacker-influenced input. A project's permissions.allow rules and additionalDirectories only apply after the developer accepts the workspace trust dialog. Hooks and the env block in settings files do not wait for that dialog in every situation: in a claude -p run or an SDK session, which never show the trust dialog, a repository's hooks, env block and helpers such as apiKeyHelper are used. If you run claude -p in CI against a repository you did not write, pass --setting-sources user so neither the project's settings files nor its .mcp.json are read. --bare also skips project hooks and .mcp.json servers, but the project's env block still applies.
What belongs in managed settings?
Managed settings are the only place a security team can set policy that developers cannot override. They arrive by one of three routes:
- Server-managed settings, set in the claude.ai admin console and fetched at startup (polled hourly)
- MDM or OS-level policy: a macOS configuration profile or a Windows
HKLMregistry value - A
managed-settings.jsonfile in a system directory
The file paths are /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/managed-settings.json on Linux and WSL, and C:\Program Files\ClaudeCode\managed-settings.json on Windows. You can split a policy across teams with a managed-settings.d/ directory next to it: files merge in alphabetical order, lists combine, and single values from the later file win.
If more than one source reaches a machine, the default is first-wins: Claude Code uses the highest-ranked source that delivers any policy key and silently ignores the rest. Server-managed settings rank first, then MDM, then files. If you use two delivery routes, either put the full policy in one or set managedSourcesBehavior to "merge" (version 2.1.242 and later).
This starter policy combines keys from the official docs. It denies two secret paths, disables bypass mode, makes managed rules the only permission rules, and requires the sandbox:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
],
"disableBypassPermissionsMode": "disable"
},
"allowManagedPermissionRulesOnly": true,
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false
}
}
Be deliberate about allowManagedPermissionRulesOnly. It makes Claude Code ignore permission rules from user, project and local files and from --allowedTools. That is strong, and it also means a team cannot add its own allow rules, so you own every approved command pattern. Many teams start without it.
Verify on one machine before a fleet push. Run /status and look for Enterprise managed settings (file) on the Setting sources line. claude doctor reports where the organization policy loaded from.
For the full key list, see the settings reference.
Can Claude Code read my .env file?
Yes, unless you stop it. Reads inside the working directory need no approval, and a .env file in your project is inside it. Block it with Read deny rules. This example is from the settings reference:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./config/credentials.json)",
"Bash(curl *)"
]
}
}
A Read deny rule excludes matching files from file discovery and search results, denies reads, and blocks the Edit and Write tools on the same path. It also applies to @file mentions, though the documentation describes the coverage of built-in readers as best effort.
Know what the rule does not cover:
- Read and Edit deny rules apply to Claude's built-in file tools, to file commands Claude Code recognizes in Bash (
cat,head,tail,sed,tee) and to redirection targets - They do not apply to a command that reads files without naming them, such as
grep -r pattern . - They do not apply to arbitrary subprocesses, such as a Python or Node script that opens the file itself
- A
.claudeignorefile has no effect at all. If your repository has one, move its entries intoReaddeny rules
For a block that applies to every process Claude starts, you need the sandbox.
Does the sandbox cover enough?
The sandbox is off by default. Turn it on with /sandbox, or with sandbox.enabled set to true. It uses Seatbelt on macOS and bubblewrap plus socat on Linux and WSL2. On native Windows, Claude Code runs commands unsandboxed.
Inside the sandbox, commands can write to the working directory and a temp directory. Network access has no direct route: connections go through a proxy that checks each host against network.allowedDomains, and that list starts empty. Reads are the weak spot. By default a sandboxed command can read most of the machine, including ~/.ssh and ~/.aws/credentials, and there is no built-in credential deny list. You add one yourself with sandbox.credentials:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}
Plan the rollout around these limits:
- The sandbox covers shell commands only. Claude's file tools, local MCP servers and hooks run outside it
- Claude can retry a failed command outside the sandbox through the
dangerouslyDisableSandboxparameter. SettingallowUnsandboxedCommandstofalseturns that off - If the sandbox cannot start, the default is to run commands unsandboxed.
failIfUnavailable: truemakes Claude Code exit instead, which also means it will exit on native Windows machines
Commands a developer types at the ! shell-mode prompt run outside the sandbox in most sessions, with the same access they have in any terminal. Details are in the sandboxing guide.
Where do hooks help?
Hooks are commands (or other handlers) that run on events. A PreToolUse hook runs before a tool call, receives the tool name and input as JSON, and can return a permissionDecision of allow, deny, ask or defer. That makes it the place for logic that rules cannot express: inspect the full command text, check a path against a list, call an internal service.
A few cautions before you lean on hooks:
PreToolUsefires only when Claude calls a tool. Files a user references with@are inserted into the prompt without a tool call, so aReadhook never sees them. Use aReaddeny rule for that case- Hooks run with your full access, outside the sandbox. A hook committed to a repository is code that runs on the developer's machine, which is why
allowManagedHooksOnlyexists: set it totruein managed settings and only hooks your organization deploys will run - A
ConfigChangehook with thepolicy_settingsmatcher lets you audit or block changes tomanaged-settings.jsonduring a session
How do you control MCP servers in Claude Code?
By default, anyone running Claude Code can connect any MCP server. Anthropic reviews connectors before listing them in its directory but does not security-audit or manage any MCP server. Servers can also arrive from a repository's .mcp.json, from plugins and from claude.ai connectors, so reading .mcp.json does not show you everything a session can load.
You have three controls:
- Exclusive set: deploy
managed-mcp.json(same directory as managed settings). Claude Code then loads only the servers in that file, plus any you provide throughmanagedMcpServers. An empty server map disables MCP. This file cannot be delivered through server-managed settings - Approved catalog: set
allowedMcpServersandallowManagedMcpServersOnly: truein managed settings - Denylist:
deniedMcpServersblocks known-bad servers and merges from every scope
An approved-catalog policy looks like this:
{
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://api.githubcopilot.com/*" },
{ "serverUrl": "https://*.internal.example.com/*" }
]
}
Match by serverUrl or serverCommand. A serverName entry is not a security control, because the name is a label the user picks and any server can be called github. Also note that allowManagedPermissionRulesOnly does not lock the MCP allowlist; allowManagedMcpServersOnly is a separate key.
In an interactive session, a .mcp.json server triggers its own approval prompt. A claude -p session shows no prompt and connects those servers anyway, so the CI advice above applies here too. For the threat side of this, see our guide to MCP security.
What does Claude Code log?
Not much, until you turn it on. Telemetry export is opt-in: set CLAUDE_CODE_ENABLE_TELEMETRY=1 and configure an OpenTelemetry exporter. The claude_code.tool_decision and claude_code.tool_result events record what was asked and what happened, and tool_decision carries the permission_mode active at the time.
The defaults are privacy-preserving, which cuts both ways for an auditor. Prompt text appears as <REDACTED> unless you set OTEL_LOG_USER_PROMPTS=1. Bash commands, file paths and MCP tool names appear only with OTEL_LOG_TOOL_DETAILS=1. Decide what you want in the record before you flip those, because logs of prompts and commands become sensitive data in their own right.
You can deliver these variables through the env block of managed settings, so they do not depend on each developer's shell.
What gaps does a team still have to fill?
Claude Code gives you a good set of controls, and some jobs sit outside them:
- Managed settings bind Claude Code only. A developer using another agent or calling an API from a script is not covered, and a developer with local admin rights can edit a file-based policy, which is why MDM redeploys it on a schedule
- Permission rules govern tool calls: tools, paths, command strings and domains. Nothing in them inspects what a developer pastes into a prompt, so an API key typed into the chat goes to the model
- The sandbox is shell-only. File tools, local MCP servers and hooks are outside it. Anything that must sit behind one boundary needs a container, a VM or the sandbox runtime around the whole process
- Telemetry gives you events. Collecting, retaining, correlating and reviewing them is your pipeline
- The documentation says plainly that no system is completely immune. Controls limit what an injected instruction can do; none of them prevents the injection
Those last gaps are the ones Rechazo was built for. It sits alongside Claude Code's own permissions and managed settings, checks prompts for credentials before they leave the company, enforces policy on the commands, file reads and MCP servers an agent touches, and keeps one audit trail across Claude Code, Codex, Gemini CLI, Cursor and Copilot. It is in waitlist stage and deployed with each customer.
What to do this week
- Pick one delivery route for managed settings (server-managed, MDM or file) and write the policy in it.
- Add
Readdeny rules for.env, secrets directories and credential files. Delete any.claudeignoreyou were relying on. - Set
permissions.disableBypassPermissionsModeto"disable", and decide ondisableAutoMode. - Turn on the sandbox with
failIfUnavailableandallowUnsandboxedCommands: false, and addsandbox.credentialsentries for~/.ssh,~/.awsand your token variables. - Choose an MCP pattern:
managed-mcp.json, orallowedMcpServerswithallowManagedMcpServersOnly. - Set
allowManagedHooksOnlyif you do not want repository hooks to run. - Enable OpenTelemetry export and decide which of the
OTEL_LOG_*variables you accept. - Run
/statusandclaude doctoron a pilot machine and confirm the policy source before you roll out. - For CI, use
--setting-sources useron repositories you do not control.
Frequently asked questions
- Is Claude Code safe to use at work?
- It can be, if you configure it. Recent versions start in auto mode, where a classifier model reviews each action and blocks risky ones, and you can enforce stricter rules for the whole organization with managed settings that developers cannot override. How safe it is depends on the rules you deploy, whether the sandbox is on and which MCP servers are allowed. Anthropic's own documentation says no system is immune to prompt injection.
- Can Claude Code read my .env file?
- Yes, by default. File reads inside the working directory do not need approval, and a .env file in your project sits inside it. To block it, add Read deny rules such as Read(./.env) and Read(./.env.*) to your settings. Those rules cover Claude's file tools and recognized shell file commands. For a hard OS-level block on shell commands, enable the sandbox.
- What is the difference between settings.json and managed-settings.json?
- settings.json holds a user's or a project's own preferences and rules, and a developer can change it. managed-settings.json is deployed by an administrator to a system directory and sits at the top of the precedence order, so no user, project or command-line setting overrides it, apart from a few documented keys where a stricter lower-level value still counts.
- How do I stop developers from adding their own MCP servers to Claude Code?
- Deploy a managed-mcp.json file, which gives every user exactly the servers it lists and blocks all others, or set allowedMcpServers together with allowManagedMcpServersOnly in managed settings. An empty server map in managed-mcp.json disables MCP entirely. Match servers by serverUrl or serverCommand, because serverName is only a label the user chooses.