The short answer first. When you bring Claude Code into an organization, the thing to design is not how clever the model is. It is what an agent running on a developer's machine, with that developer's permissions, may do without asking, what it must confirm, and what it may never do. You then put those decisions into managed settings that developers can't override, and cover what permission rules can't stop with the sandbox and device management.
How an individual or a small team hardens its own settings.json is covered in Running Claude Code in production — permissions, hooks, CLAUDE.md and security gates. This article picks up where that one stops: the design a CTO, infrastructure lead or security lead applies when handing Claude Code to a whole team. Whether to adopt a given MCP server is split out into Trusting and governing third-party MCP servers.
Every setting key, path and default here was checked against the official documentation (code.claude.com/docs) as retrieved on 2026-10-12. Claude Code ships quickly and many behaviors depend on the version, so confirm them against the version you actually deploy.
1. Threat model: an agent running with the developer's own permissions
What you are protecting is not "the Claude Code tool" but the developer's machine and everything it can reach. Claude Code runs locally, reads and writes files, and runs shell commands with the developer's user permissions. The official Security page says the working-directory boundary is only a permission prompt, and that "a Bash command you approve can still write anywhere your user account can."
Thinking about the blast radius in five channels keeps you from missing anything.
| Channel | What can happen | Main controls |
|---|---|---|
| Files | Reading secrets, rewriting files outside the working directory | Read/Edit rules, sandbox filesystem settings |
| Shell | Running arbitrary commands, getting around rules via child processes | Permission modes, Bash rules, the sandbox |
| Network | Exfiltration, connecting to unapproved hosts | Sandbox network settings, WebFetch rules |
| Credentials | Leaking ~/.ssh, ~/.aws or environment variables | denyRead, sandbox.credentials, scrubbing the environment |
| CI | Runs nobody is watching, instructions planted in untrusted pull requests | --bare, dontAsk, the Action's trigger rules |
The entry point that deserves the most attention is prompt injection: instructions hidden in outside content (an issue body, a web page, an MCP server's response) that try to take over the agent's decisions. The basic stance for an organization is not to rely on the model's judgment, but to use settings to define a range the agent can't reach even if its judgment is hijacked.
Before everyone on the team gets it
Permissions and governance for using Claude Code safely as an organization
2. Permission modes: which of the six to allow, and for whom
As of October 2026 there are six permission modes. The first organizational decisions are to keep bypassPermissions off developer machines, and whether to allow auto mode.
| Mode | What runs without asking | Intended use (per the docs) | Example organizational policy |
|---|---|---|---|
default (shown as Manual) | Reads only | Reviewing every action yourself, sensitive work | Allowed for everyone; the default for sensitive repositories |
acceptEdits | Reads, file edits and common filesystem commands such as mkdir | Iterating on code you're reviewing | Allowed for day-to-day development |
plan | Reads (plus classifier-approved commands when auto mode is available) | Exploring before changing anything | Allowed for everyone |
auto | Everything, with background safety checks | Long tasks, less prompt fatigue | A policy call; to block it, disableAutoMode |
dontAsk | Reads in the working directories and pre-approved tools; anything that would prompt is denied | Locked-down CI and scripts | CI only |
bypassPermissions | Everything | Isolated containers and VMs only | Disabled on developer machines |
In auto mode, a separate classifier model reviews actions instead of the user and blocks the ones it judges unsafe. Note that from v2.1.283, auto is the starting mode for sessions launched in a terminal. To block it organization-wide, set disableAutoMode (also accepted as permissions.disableAutoMode) to "disable" in managed settings.
bypassPermissions removes the prompts and safety checks. The docs warn to use it only in isolated environments such as containers, VMs or dev containers without internet access. Setting permissions.disableBypassPermissionsMode to "disable" also makes Claude Code reject the --dangerously-skip-permissions flag.
Deny rules work regardless of mode: the docs state that "deny rules block in every mode, including bypassPermissions." Also, defaultMode: "bypassPermissions" or "auto" in a project's .claude/settings.json doesn't take effect from that file, so a cloned repository can't loosen the mode on its own.
3. The allow / ask / deny order, and denies that cross scopes
Rules are evaluated deny, then ask, then allow, and the first match decides the outcome. Writing a more specific rule doesn't change that order.
The order also crosses scopes. In the docs' words, "a user-level deny blocks a project-level allow, because deny rules from any scope are evaluated before allow rules." A deny in managed settings can't be overridden by --allowedTools, while --disallowedTools can add further restrictions.
That has two implications for an organization.
- Anything you put in deny loses to no allow anywhere below it. Put the operations you must never let through, such as reading secrets or
git push --force, in the managed deny list. - Allow widens access, so you may want to control where it comes from. With
allowManagedPermissionRulesOnly: true, Claude Code ignores allow, ask and deny rules from user, project, local and--settingsfiles, ignores--allowedTools, and hides the "always allow" choices. It is powerful, but it increases prompt fatigue, so rolling it out first to the most regulated teams is the realistic path.
Note also that permissions.allow rules in a project's .claude/settings.json apply only after someone accepts the workspace trust dialog for that folder. Deny and ask rules aren't affected, since they only restrict.
4. Where rule syntax stops: neither Bash rules nor Read denies are a boundary
Permission rules match the form of command Claude usually writes. They are not a fence around the program itself. Misread this and you leave holes you think you have closed.
For Bash rules, the docs say plainly that a deny or ask rule "isn't a security boundary around the program." A deny on Bash(curl *) doesn't match /usr/bin/curl https://example.com or sh -c 'curl https://example.com'. And curl and wget are merely not auto-approved by default; they are not on a built-in blocklist. If you want them stopped, put them in deny, and if the restriction must hold, combine that with the sandbox network allowlist.
Read and Edit denies are similar. They apply to the built-in file tools, to file commands Claude Code recognizes such as cat, head and sed, and to redirection targets like > file. They don't apply to grep -r pattern ., which reads files without naming them, or to a Python or Node script that opens files itself. A .claudeignore file has no effect, so if you rely on one, move its entries into Read deny rules.
Path syntax has its own trap. /path is anchored at the settings file's location, not the filesystem root. Read(/secrets/**) in user settings blocks ~/.claude/secrets/**. Write absolute paths as //path and home-relative paths as ~/path.
5. The settings hierarchy and how to deliver managed settings
Managed settings are the top layer: no user setting, project setting or --settings value overrides them. From highest to lowest, the order is managed settings → --settings on the command line → .claude/settings.local.json → .claude/settings.json → ~/.claude/settings.json. List keys such as permissions.allow are merged rather than overridden.
There are four delivery mechanisms.
- File:
/Library/Application Support/ClaudeCode/managed-settings.jsonon macOS,/etc/claude-code/managed-settings.jsonon Linux and WSL, andC:\Program Files\ClaudeCode\managed-settings.jsonon Windows. The legacyC:\ProgramData\ClaudeCode\managed-settings.jsonis not read. Split files in amanaged-settings.d/directory next to it are merged in alphabetical order. - MDM / OS policy: the
com.anthropic.claudecodemanaged preferences domain on macOS, and aREG_SZorREG_EXPAND_SZvalue namedSettingsunderHKLM\SOFTWARE\Policies\ClaudeCodeon Windows. - Server-managed settings: delivered from the claude.ai admin console. They require Claude for Teams or Claude for Enterprise, and the Owner or Primary Owner role to edit.
- HKCU registry: user-writable, so it doesn't count as an admin source.
The easily missed part is what happens when you use more than one mechanism. By default (managedSourcesBehavior set to "first-wins"), Claude Code uses only the highest-ranked source that contains a policy key and ignores the rest instead of merging them. The ranking is server-managed → MDM → file → HKCU. So you can try out server-managed settings briefly and find that the sandbox policy you were delivering by file has stopped applying. If you need both, consider managedSourcesBehavior: "merge", and use /status to see which source is in effect.
Server-managed settings come with two more constraints.
- The docs state that they "operate as a client-side control, not a security boundary. On unmanaged devices, a user doesn't need admin or sudo access to bypass them."
- If a provider variable such as
CLAUDE_CODE_USE_BEDROCK, or a non-defaultANTHROPIC_BASE_URL, is exported in the shell, the fetch is skipped. Organizations on Amazon Bedrock, Google Cloud's Agent Platform (formerly Vertex AI) or Microsoft Foundry deliver policy through endpoint management or a self-hosted Claude apps gateway instead.
6. Managed-only keys versus keys any file can set
Keep two kinds of keys apart: keys that are enforced because they sit in managed settings, and keys any file can set, which become non-overridable only once you place them in managed settings. The Scope line in the settings reference is the authority.
| Kind | Main keys | What it means |
|---|---|---|
| Managed only | allowManagedPermissionRulesOnly, allowManagedHooksOnly, allowManagedMcpServersOnly, managedMcpServers, strictKnownMarketplaces, blockedMarketplaces, disableSideloadFlags, sandbox.network.allowManagedDomainsOnly, sandbox.filesystem.allowManagedReadPathsOnly, forceRemoteSettingsRefresh | Locks that have no effect outside managed settings |
| Any file | permissions.disableBypassPermissionsMode, disableAutoMode, allowedMcpServers, deniedMcpServers, enableAllProjectMcpServers, sandbox.enabled, sandbox.allowUnsandboxedCommands, cleanupPeriodDays, env | Can't be overridden when set in managed settings, but also work from lower files |
| Exceptions where a stricter lower value wins | permissions.blockReadsOutsideWorkingDirectories: true, disableClaudeAiConnectors: true and others | A true from any scope applies, even over a managed false |
That allowedMcpServers is not managed-only is especially easy to get wrong. Unless you also set allowManagedMcpServersOnly, users can widen the allowlist in their own settings. The MCP article covers this in detail.
7. The sandbox: off by default, and ~/.ssh is readable even when on
The sandbox is off by default. Even when it is on, by default it can read most of the machine, including ~/.ssh and ~/.aws/credentials. For organizational use, put enabling it and blocking those reads into managed settings together.
The sandbox wraps the shell commands Claude runs, and their child processes, with Seatbelt on macOS and bubblewrap on Linux and WSL2. Its defaults are:
- Writes: the working directory, a per-user temp directory and directories you've added.
- Reads: most of the machine. Close paths off with
sandbox.filesystem.denyRead; paths in your Read deny rules are merged in as well. - Network: no direct route out. A local proxy checks each host against
allowedDomains, which starts empty. - Environment variables: inherited from Claude Code, so any secrets there are visible.
Know what runs outside it, too. Built-in tools such as Read, Edit, Write, WebFetch and WebSearch follow permission rules rather than the sandbox (denyRead doesn't stop the Read tool). Command hooks, local MCP servers, LSP servers, the status line command and apiKeyHelper run with the user's full access.
The docs' configuration for enforcing it is three keys: enabled: true, failIfUnavailable: true and allowUnsandboxedCommands: false. Without the last one, Claude can retry a command that failed inside the sandbox outside it, using dangerouslyDisableSandbox. With failIfUnavailable, Claude Code exits at startup on native Windows, where the sandbox doesn't run, so either move Windows developers to WSL2 or a container, or deliver a different policy per operating system.
Keep allowed domains narrow. The proxy doesn't terminate or inspect TLS by default, and the docs warn that allowing a broad domain such as github.com can create paths for exfiltration, including through domain fronting. Allowing /var/run/docker.sock via allowUnixSockets effectively hands over access to the host. To keep developers from widening the policy, consider allowManagedDomainsOnly and allowManagedReadPathsOnly.
8. Secrets: where they live and how to hide them
Think about secrets in three layers: don't let the tools read them, don't pass them to child processes, and don't leave them in plaintext.
- Don't let tools read them: write both a Read deny (for the built-in tools) and
sandbox.filesystem.denyRead(for shell commands). If a secret file has to stay usable while its contents stay hidden,sandbox.credentialslets you setdenyormaskper file or variable. - Don't pass them on: with
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, credentials are stripped from the environments of the subprocesses Claude Code starts, such as Bash commands, hooks and stdio MCP servers (GitHub tokens and proxy settings are left in place). - Don't leave them in plaintext: values in a settings file's
envblock are plaintext and reach every subprocess. Fetch API credentials from a vault on demand withapiKeyHelper. Session transcripts are also stored in plaintext under~/.claude/projects/, for 30 days by default; shorten that withcleanupPeriodDays.
Claude Code's own API keys and tokens are stored in the macOS Keychain when available, and on Linux in a file with mode 0600.
9. Hooks: run only the ones the organization ships
Hooks run shell commands with the user's full permissions, so whose hooks run is itself something to govern. With allowManagedHooksOnly: true in managed settings, only managed hooks, Agent SDK hooks and hooks from plugins that managed settings force-enable will run; user, project and local hooks are blocked.
Conversely, hooks the organization ships can't be turned off by disableAllHooks in a lower scope. A PreToolUse hook returning "allow" doesn't override a deny rule, so hooks are safest as reinforcement in the blocking direction.
For auditing configuration changes, use a ConfigChange hook. It fires when user, project or local settings files, or skill files, change during a session, and it can log or block the change. It can't block a change to managed files, though, and it doesn't fire at all for changes to server-managed settings, macOS managed preferences or the Windows registry.
10. Data handling: training, retention and ZDR
Under commercial terms (Team, Enterprise, API and third-party platforms), code and prompts sent to Claude Code are not used for training unless the customer chooses to provide them. When checking against internal policy, cover these points.
- Standard commercial retention is 30 days.
- Transcripts sent with
/feedbackand similar commands are retained for 5 years. Decide how to treat that when they contain work code. - Zero data retention (ZDR) is for qualified Claude for Enterprise accounts. It is not included in the standard Enterprise plan and cannot be enabled from admin settings; it needs separate enablement by Anthropic and applies only to Anthropic's direct platform.
- Traffic is encrypted with TLS 1.2 or later. The usage metrics Claude Code sends never include code, prompts or file paths, and
DISABLE_TELEMETRY=1turns them off.
11. Observability and audit
Usage can be exported to your own collector with OpenTelemetry, and administrators can set this for everyone through the env block in managed settings. Set CLAUDE_CODE_ENABLE_TELEMETRY=1 and the OTEL_* exporters. When managed settings name the destination, developer-set destinations are removed at startup, and a repository's settings files can't set the exporter variables at all.
On the privacy side, logging prompt text (OTEL_LOG_USER_PROMPTS) is off by default, as is logging tool arguments (OTEL_LOG_TOOL_DETAILS). Decide what to record together with your employment and personal-data policies.
On Team and Enterprise, Admins and Owners can see usage in the claude.ai analytics dashboard. For audit logs of settings changes, the docs say they are available through the compliance API or audit log export, and direct you to your Anthropic account team.
12. CI: the -p trust trap and GitHub Actions
The most dangerous thing in CI is that claude -p never shows the workspace trust dialog. Without --bare, it runs the hooks in the repository's .claude/settings.json and connects the servers in its .mcp.json without asking, even in a folder you have never trusted. The docs recommend --bare for scripted calls and the SDK. The project's env block still applies under --bare, though, so don't rely on it alone.
The basic shape for a CI job is --permission-mode dontAsk plus --allowedTools listing exactly the tools it may use, so anything that would prompt is denied.
With GitHub Actions (anthropics/claude-code-action), settle these points first.
- Who can trigger it: on issue and pull request events, the triggering user must have write access to the repository. Bypassing that with
allowed_non_write_usersis described as "a significant security risk." Bots are rejected unless listed inallowed_bots. - Configuration on pull requests: when it runs against a pull request, the action restores configuration paths such as
.claude/,.mcp.jsonandCLAUDE.mdfrom the base branch before starting Claude, so Claude doesn't run with configuration the pull request rewrote. pull_request_targetandworkflow_run: these run with the base repository's secrets, so never check out an untrusted ref into the workspace root before the action.- Hidden instructions: the action strips HTML comments, invisible characters and similar content, but its docs acknowledge that new bypass techniques may emerge. Keep permissions minimal for workflows where Claude reads issues or pull requests written by outsiders.
- API keys: never commit them; store them as GitHub Secrets.
13. What each setting prevents, and what it doesn't
No setting is complete on its own. In a rollout review, use this table to find the gaps.
| Setting | What it prevents | What it doesn't prevent |
|---|---|---|
Read(...) in permissions.deny | Reads through the built-in file tools and recognized commands | Reads by scripts or subprocesses that open files themselves |
Bash(curl *) in permissions.deny | curl in the form Claude usually writes | A full path, sh -c, or a different network tool |
disableBypassPermissionsMode | Bypass mode, including --dangerously-skip-permissions | Auto mode (that needs disableAutoMode) |
sandbox.enabled + allowUnsandboxedCommands: false | Shell commands and their children going beyond allowed files and hosts | Hooks, local MCP servers, built-in tools, commands the user types at ! |
sandbox.filesystem.denyRead | Sandboxed commands reading secret files | The Read tool (needs a Read deny) |
sandbox.network.allowedDomains | Connections to hosts outside the list | Exfiltration through a broadly allowed domain, TLS contents |
| Server-managed settings | Overrides on managed devices | Bypass on unmanaged devices, sessions on third-party providers |
allowManagedHooksOnly | Hooks brought in by individuals or repositories | Defects in the hooks the organization ships |
--bare (CI) | Auto-loading the repository's hooks, skills and .mcp.json | The project's env block |
14. An example baseline managed-settings.json
The example below is a starting point built only from keys confirmed in the official docs. Don't ship it as is: change the values to match your package registry and internal MCP servers, and verify it with a pilot group before widening it.
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(secrets/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(curl *)",
"Bash(wget *)"
],
"ask": [
"Bash(git push *)"
],
"disableBypassPermissionsMode": "disable"
},
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"],
"allowManagedReadPathsOnly": true
},
"network": {
"allowedDomains": ["registry.npmjs.org", "packages.example.internal"],
"allowManagedDomainsOnly": true
}
},
"allowManagedHooksOnly": true,
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://mcp.example.internal/*" }
],
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.example.internal:4317"
},
"cleanupPeriodDays": 14
}
Some notes on the intent.
- The
Bash(curl *)deny only stops the usual form. What reliably stops exfiltration is the sandbox's network allowlist. github.comis deliberately not in the allowed domains, because a broad domain can become an exfiltration path. If git operations need it, either add it knowing the trade-off, or move specific commands out withexcludedCommands(excluded commands run with full access).disableAutoMode, which blocks auto mode, is not included. Decide that as a policy matter and add it here if you block it.- Because of
failIfUnavailable, deliver a different file to native Windows machines or move those users to WSL2. - After delivering it, open
/statuson one machine and confirm theSetting sourcesline showsEnterprise managed settings (file).
15. A staged rollout: pilot → policy → managed → CI
Pushing a strict policy to everyone on day one multiplies exception requests and workarounds in personal settings. Four stages are the realistic path.
- Pilot: with a small team, work mainly in Manual and acceptEdits, and record which commands, domains and MCP servers are actually needed. Review the real permissions regularly with
/permissions. - Policy: from the pilot's record, decide what everyone may do, what needs confirmation and what is forbidden. Also decide here whether to allow auto mode, which MCP servers to approve and where secrets live.
- Managed settings: deliver the policy by file or MDM. If you also use server-managed settings, plan around the first-wins behavior and the fact that unmanaged devices can bypass them. Watch that it is in effect with
ConfigChangehooks and OpenTelemetry. - CI: finally, lock down unattended runs with
--bare,dontAsk,--allowedToolsand the Action's trigger rules.
Summary: governing the tool and reviewing what it built are different jobs
Rolling Claude Code out to an organization means deciding how far an agent running with the developer's permissions may go. Write the denies first, put them in managed settings so they can't be overridden, and cover what permission rules can't reach with the sandbox and device management. Above all, don't treat as a boundary anything the docs themselves say is not one.
This is about using the tool safely. Finding vulnerabilities in the app you built with Claude Code is a separate job; if you need the app's authorization or RLS checked, see Security reviews for AI-generated code.
If you want outside help turning your policy into managed settings, sandbox configuration, an MCP allowlist and CI settings, I take enquiries at Claude Code security governance for organizations.