Prelude

Consider a platform engineering team with forty developers who have adopted Claude Code. Productivity is through the roof. The engineering manager is thrilled. Then the CISO walks in, asks one question, and the room goes quiet.

"How do you know what it's doing?"

Nobody has an answer. Each developer has configured Claude Code independently. Some have granted it full Bash access. Others have installed MCP servers from unknown sources. A few have written custom hooks that pipe session data to personal endpoints. There is no central policy, no audit trail, and no way to enforce one retroactively.

This scenario plays out in organisations everywhere right now. Claude Code is powerful enough that developers adopt it on their own. But individual adoption without central governance creates exactly the kind of security gaps that keep CISOs awake at night.

This guide is the answer to that question. Not a policy document. A technical walkthrough of how to deploy Claude Code across an organisation with centrally managed settings that developers cannot override, permissions that follow the principle of least privilege, and hooks that give you a complete audit trail of every session.

The Problem

The core tension is simple. Developers want autonomy. Security teams want control. Claude Code sits right in the middle of that tension because it is an AI agent that can read files, execute commands, make network requests, and modify code.

Without central management, every developer's Claude Code installation is a standalone island. Each person decides for themselves what Claude can access, what commands it can run, what data it can touch. There is no way for the organisation to say "these are our standards" and have that enforced at the tool level.

The default Claude Code permissions system is designed for individual developers. It has a tiered permission model where read-only tools like file reads and Grep need no approval, Bash commands need approval on first use, and file modifications need approval each session. That is sensible for a solo developer. It is inadequate for an organisation with fifty or five hundred developers who all need to follow the same data handling policies.

The gap between individual configuration and enterprise policy is where managed settings live.

The Journey

Understanding the Settings Hierarchy

The first thing to understand is that Claude Code has a clear precedence hierarchy for settings. From highest to lowest priority, the levels are managed settings, command line arguments, local project settings in .claude/settings.local.json, shared project settings in .claude/settings.json, and user settings in ~/.claude/settings.json.

Managed settings sit at the top. They cannot be overridden by any level below them, including command line arguments. A developer cannot bypass them with a flag, a local config file, or a project-level override. They are the ceiling.

This is the foundation everything else builds on. If you configure a deny rule in managed settings, no developer can undo it. If you set allowManagedHooksOnly to true, no developer can add their own hooks. The hierarchy is not a suggestion. It is enforced by the runtime.

Deploying Managed Settings Across Your Fleet

The managed-settings.json file lives in a system directory that requires administrator privileges to modify. The paths differ by operating system.

On macOS, the file goes to /Library/Application Support/ClaudeCode/managed-settings.json. On Linux and WSL, it goes to /etc/claude-code/managed-settings.json. On Windows, it goes to C:\Program Files\ClaudeCode\managed-settings.json.

These are not in the user's home directory. They are not in the project folder. They are in system-level paths that regular developers cannot write to without elevated privileges.

You have three delivery mechanisms to choose from.

File-based deployment is the simplest. Script the file drop using Ansible, Chef, Puppet, or a plain shell script. Copy managed-settings.json to the appropriate system path. This works for any organisation that already manages system configuration through automation.

MDM deployment uses platform-native management. On macOS, deploy through Jamf, Kandji, or any MDM that supports managed preferences, using the domain com.anthropic.claudecode. On Windows, push settings through Group Policy or Intune via the HKLM\SOFTWARE\Policies\ClaudeCode registry key with a Settings value containing the JSON configuration.

Server-managed settings push configuration remotely from the Claude.ai admin console with no file on disk at all. This is the cleanest option for organisations already managing their Anthropic subscription centrally.

Only one managed source wins. The precedence within the managed tier is server-managed first, then MDM and OS-level policies, then the managed-settings.json file. Sources do not merge. The highest-priority source that exists is the one that takes effect.

The Permissions Model That Actually Works

Most teams get the permissions model wrong on day one. The instinct is to start open and block specific things. "Let developers do everything except these dangerous commands." That is a losing game.

Every new tool, every new script, every new pattern needs someone to remember to add it to the blocklist. Something always slips through. The blocklist grows forever and still has gaps.

The better approach is allowlisting. Start with nothing permitted. Add only what your team actually needs. The permissions documentation supports this through a clear rule evaluation order where deny rules are checked first, then ask rules, then allow rules. The first matching rule wins.

A practical starting configuration for a managed-settings.json looks like this.

{
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(npm run build)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(//**/credentials*)"
    ]
  },
  "allowManagedPermissionRulesOnly": true
}

The critical line is allowManagedPermissionRulesOnly. When this is true, user and project settings cannot define their own allow, ask, or deny rules. Only the rules in managed settings apply. A developer cannot override your permissions at any level.

Without that flag, a developer could add "allow": ["Bash"] to their user settings and grant themselves unrestricted Bash access. With the flag enabled, their user-level permission rules are simply ignored.

One important detail about Bash wildcard patterns. The space before the asterisk matters. Bash(ls *) matches ls -la but not lsof, because the space enforces a word boundary. Bash(ls*) without the space matches both. Claude Code is also aware of shell operators, so a rule like Bash(npm run *) will not accidentally allow npm run test && rm -rf /.

Locking Down Hooks

Hooks are one of the most powerful features in Claude Code. They are also one of the biggest security surface areas. A hook is a user-defined shell command, HTTP endpoint, or LLM prompt that executes automatically at specific points in a Claude Code session.

There are multiple hook events. PreToolUse fires before Claude uses any tool and can approve, deny, or modify the tool call. PostToolUse fires after a tool completes. Notification fires when Claude produces a status notification. Stop fires when Claude finishes its response. SubagentStop fires when a subagent completes. And several more.

In an unmanaged environment, any developer can define hooks in their user settings, project settings, or through plugins. A malicious or misconfigured hook could exfiltrate data through an HTTP endpoint, modify tool inputs to bypass security policies, or silently alter Claude's outputs.

The fix is one setting in your managed-settings.json.

{
  "allowManagedHooksOnly": true
}

When allowManagedHooksOnly is enabled, every user-defined hook, every project hook, and every plugin hook is blocked. Only the managed hooks run. The developer sees them running in their session but cannot modify, disable, or add their own.

Hooks snapshot at session start. Even if someone modified a settings file mid-session, the changes do not take effect until the next session. Claude Code warns the developer and requires review before anything applies. This prevents malicious pull requests that sneak hook changes into .claude/settings.json from taking effect immediately.

Building Your Audit Trail with HTTP Hooks

Now that only managed hooks can run, you can use them to build a comprehensive audit trail. HTTP hooks send the event's JSON input as a POST request to your endpoint, with the response using the same JSON output format as command hooks.

Here is a managed hook configuration that sends every tool use to your logging endpoint.

{
  "hooks": {
    "PostToolUse": [
      {
        "type": "http",
        "url": "https://audit.yourcompany.com/claude-code/tool-use",
        "matcher": "",
        "timeout": 5000,
        "headers": {
          "Authorization": "Bearer $AUDIT_API_KEY",
          "X-Developer-Email": "$DEVELOPER_EMAIL"
        },
        "allowedEnvVars": ["AUDIT_API_KEY", "DEVELOPER_EMAIL"]
      }
    ],
    "Stop": [
      {
        "type": "http",
        "url": "https://audit.yourcompany.com/claude-code/session-end",
        "timeout": 5000,
        "headers": {
          "Authorization": "Bearer $AUDIT_API_KEY"
        },
        "allowedEnvVars": ["AUDIT_API_KEY"]
      }
    ]
  }
}

The headers field supports environment variable interpolation, but only for variables explicitly listed in allowedEnvVars. This prevents accidental secret leakage. Even if a hook configuration references $AWS_SECRET_KEY, it resolves to empty unless explicitly approved in the allowedEnvVars list.

You can further restrict this globally with httpHookAllowedEnvVars in managed settings. Each hook's own allowedEnvVars list is intersected with the global list. If the global list does not include a variable, no hook can access it regardless of its own configuration.

For URL restrictions, allowedHttpHookUrls whitelists which URLs hooks can target. An empty array blocks all HTTP hooks. This prevents any hook from reaching an endpoint you have not explicitly approved.

Controlling MCP Servers and Network Access

MCP servers are another surface area that needs central control. An MCP server is an external tool provider that Claude Code can connect to, giving it capabilities like database access, API integrations, or file system operations beyond the working directory.

In managed settings, allowManagedMcpServersOnly restricts which MCP servers are available. When enabled, only the servers listed in your managed MCP configuration can be used. Denied servers still merge from all sources, so you can blocklist at any level.

For network access, the sandbox configuration provides OS-level enforcement. The sandbox.network.allowManagedDomainsOnly setting ensures that only domains listed in managed settings are accessible. Non-allowed domains are blocked automatically without even prompting the developer.

{
  "allowManagedMcpServersOnly": true,
  "sandbox": {
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": [
        "api.github.com",
        "registry.npmjs.org",
        "audit.yourcompany.com"
      ]
    }
  }
}

This creates a layered defence. Permissions control what Claude decides to do. The sandbox controls what actually happens at the OS level, even if a prompt injection bypasses Claude's decision-making.

Compliance and Audit Requirements

Managed settings are not just a security convenience. For many organisations, they are a compliance requirement. If your company is SOC 2 certified, undergoing ISO 27001 audits, or subject to GDPR obligations around automated data processing, you need demonstrable controls over AI agent behaviour.

SOC 2 Trust Services Criteria require that organisations define and enforce access controls for systems that process customer data. An unmanaged Claude Code installation where each developer sets their own permissions does not meet that bar. Managed settings give you a single, auditable configuration that proves every developer's Claude Code session operates under the same policy. Your auditor can inspect one file and understand what the agent can and cannot do.

For GDPR compliance, the key concern is automated processing of personal data. If Claude Code reads files that contain customer information, you need to demonstrate that the processing is controlled and limited. Deny rules that block access to production data directories, combined with audit hooks that log every file read, give you the evidence trail that Article 30 record-keeping requires.

ISO 27001 Annex A controls around access management (A.9) and operations security (A.12) map directly to managed settings capabilities. The permissions allowlist satisfies access control requirements. The hook-based audit trail satisfies logging and monitoring requirements. The disableBypassPermissionsMode setting satisfies the requirement that security controls cannot be circumvented by end users.

Beyond formal compliance, many organisations have internal security policies that require all developer tools to be centrally managed. Managed settings turn Claude Code from an unmanaged developer tool into a centrally governed one, which is often the difference between "approved for use" and "blocked by security" in enterprise procurement reviews.

Deployment Patterns in Practice

The choice of deployment mechanism depends on what your organisation already uses for system configuration management.

Git-based configuration management works well for teams that treat infrastructure as code. Store your managed-settings.json in a dedicated repository alongside other system configurations. Use a CI/CD pipeline to validate the JSON on every pull request, run a schema check against the Claude Code settings schema, and deploy the file to target machines through your existing provisioning tooling. This gives you version history, peer review on policy changes, and rollback capability.

MDM distribution for macOS through Jamf or Kandji is the cleanest option for organisations with Apple fleets. Create a configuration profile targeting the com.anthropic.claudecode preference domain. The JSON settings map directly to the profile payload. Push updates through your MDM console and they take effect on each developer machine at next check-in. This approach also gives you MDM-level reporting on which machines have received the configuration.

Windows Group Policy distribution follows the standard GPO workflow. Create a new GPO, navigate to Computer Configuration > Administrative Templates, and set the registry value at HKLM\SOFTWARE\Policies\ClaudeCode\Settings with your JSON configuration as the string value. Link the GPO to the appropriate organisational unit. For Intune-managed devices, deploy the same registry key through a configuration profile.

CI/CD validation is worth adding regardless of your deployment mechanism. A simple pipeline step that runs python -m json.tool managed-settings.json catches syntax errors before they reach developer machines. A more thorough check validates that every permission rule follows the correct Tool(pattern) syntax and that all hook URLs are reachable. Deploying invalid JSON to the managed settings path silently breaks the configuration, which means developers fall back to their own settings with no central control. Catching errors in the pipeline prevents that.

The Complete Enterprise Lockdown

Bringing all of this together, here is a full managed-settings.json that represents a production enterprise deployment.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git commit *)",
      "Bash(* --version)",
      "Bash(* --help *)"
    ],
    "deny": [
      "Bash(git push --force *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(rm -rf *)",
      "Read(./.env)",
      "Read(//**/credentials*)",
      "Read(///**/.ssh/**)"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "allowManagedHooksOnly": true,
  "allowManagedMcpServersOnly": true,
  "disableBypassPermissionsMode": "disable",
  "sandbox": {
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": [
        "api.github.com",
        "registry.npmjs.org",
        "audit.yourcompany.com"
      ]
    }
  },
  "hooks": {
    "PostToolUse": [
      {
        "type": "http",
        "url": "https://audit.yourcompany.com/claude-code/tool-use",
        "matcher": "",
        "timeout": 5000,
        "headers": {
          "Authorization": "Bearer $AUDIT_API_KEY"
        },
        "allowedEnvVars": ["AUDIT_API_KEY"]
      }
    ]
  }
}

Every layer is enforced centrally. Permissions are managed-only. Hooks are managed-only. MCP servers are managed-only. Bypass mode is disabled. Network access is restricted to approved domains. An audit hook captures every tool use. Nothing is left to the individual developer's configuration.

Developers verify their managed settings source by running /status in Claude Code. They can see what policies apply to their session. They just cannot change them.

Troubleshooting Common Deployment Issues

Even a well-planned deployment runs into problems. Here are the issues that come up most often and how to resolve them.

Malformed JSON in managed-settings.json. If the file contains invalid JSON, Claude Code cannot parse it. The result is that managed settings are silently ignored and developers fall back to their own user and project settings. This is the worst failure mode because it looks like everything is working while no central policy is actually enforced. Always validate your JSON before deployment. Run python -m json.tool managed-settings.json or use jq . managed-settings.json as a pre-deployment check. If developers report that /status shows no managed settings source, invalid JSON is the first thing to investigate.

MDM and file-based settings both present. If you have both an MDM-delivered configuration and a managed-settings.json file on the same machine, only one wins. The precedence within the managed tier is server-managed first, then MDM, then the file. The sources do not merge. If your MDM configuration has permissions but no hooks, and your file has hooks but no permissions, the MDM configuration wins entirely and the hooks from the file are ignored. Pick one delivery mechanism per machine and stick with it.

Permission rules not matching as expected. The most common cause is wildcard confusion. Remember that Bash(git *) matches git status and git diff --staged but not gitk, because the space before the asterisk creates a word boundary. If a developer reports that a command they expect to be allowed is being blocked, check whether the pattern in your allow rule actually matches the full command string. Ask the developer to share the exact command that was blocked. Then test the pattern against it. Also remember that rule evaluation is deny first, then ask, then allow. If you have a deny rule that matches more broadly than you intended, it takes precedence over any allow rule.

Developers cannot see managed settings in /status. This usually means the file is in the wrong path or has incorrect file permissions. On macOS, verify the file is at /Library/Application Support/ClaudeCode/managed-settings.json and is world-readable. On Linux, check /etc/claude-code/managed-settings.json. On Windows, check C:\Program Files\ClaudeCode\managed-settings.json. The file needs to be readable by the developer's user account, even though it should not be writable by them. A file owned by root with 644 permissions is the correct setup on macOS and Linux.

Audit hooks returning errors. If your HTTP audit hook endpoint is unreachable or returns an error, the hook fails but Claude Code continues operating. Hooks do not block the session on failure by default. This means a network outage at your audit endpoint does not break developer productivity, but it does create a gap in your audit trail. Monitor your audit endpoint's availability independently. If you need hooks to be blocking, where a failed hook prevents the tool use from proceeding, use a PreToolUse hook instead of PostToolUse. A PreToolUse hook that returns an error will block the tool call entirely.

Settings changes not taking effect mid-session. This is by design. Claude Code snapshots hook and settings configuration at session start. If you push an updated managed-settings.json while a developer has an active session, they will not see the changes until they start a new session. For urgent policy changes, you may need to communicate to developers that they should restart their Claude Code sessions. There is no remote kill switch for active sessions through managed settings alone.

The Lesson

Enterprise AI agent management is not a policy document. It is a centrally deployed configuration that intercepts every critical agent action, enforces your data handling rules, and gives you a complete audit trail.

The organisations that are deploying Claude Code successfully are not the ones with the most restrictive policies. They are the ones that found the right balance. Developers still have autonomy within their working directory. They can still use Claude Code for everything it is good at. But the guardrails are non-negotiable and centrally enforced.

The managed settings system is what makes this possible. Not trust. Not training. Not documentation that people might read. A configuration hierarchy where the organisation's policies sit at the top and cannot be overridden at any level below.

If your team is using Claude Code without managed settings, every developer is making independent security decisions about an AI agent that can execute arbitrary commands. For a solo developer, that is fine. For a team of ten or more, it is a risk that grows with every person you onboard.

The good news is that the infrastructure exists today. The managed settings documentation covers every option. The permissions model supports fine-grained control. The hooks system gives you the audit trail. All that remains is deploying it.

Conclusion

The CISO who asks "how do you know what it's doing?" is asking the right question at the wrong time. The answer should already exist before the question comes up.

A managed-settings.json file deployed to every developer machine through your existing MDM or configuration management gives you that answer. Every permission rule, every hook, every MCP server, every network domain is defined centrally and enforced by the runtime. The audit trail captures what happened. The permissions ensure only approved actions are possible.

Start with a minimal allowlist. Deploy the managed settings file to a test group. Enable the audit hooks and review what Claude Code actually does during a typical development session. Expand the allowlist based on real usage, not assumptions. Then roll it out to everyone. For a detailed week-by-week rollout plan covering team onboarding, training, and scaling beyond the initial pilot, see our organisation rollout guide.

Enterprise adoption of AI coding agents is not a question of whether. It is a question of how. And the how is managed settings.