Securing Claude Code in the enterprise: managed settings, hooks and what they don't cover
Managed settings, managed MCP configuration and hooks give an administrator real control over Claude Code on a laptop. Here is how each works, and the four things they leave to you.
Claude Code's enterprise controls are three mechanisms: managed settings files or server-managed settings, the managed MCP configuration, and hooks. Together they set permission rules, restrict which MCP servers can load, and block a tool call before it executes. The paths and setting names below come from Anthropic's documentation for Claude Code v2.1, and the last section covers what an administrator still owns.
Settings files, and which one wins
Claude Code reads settings from four files, and an organization adds a managed layer above them. Anthropic's settings reference lists them with the scope each one reaches.
| Scope | File | Who it affects |
|---|---|---|
| User | ~/.claude/settings.json | That user, in every project on the machine |
| Shared project | .claude/settings.json | Everyone working in the repository |
| Project local | .claude/settings.local.json | That user, in that project |
| Managed | managed-settings.json and the other managed sources | Everyone the organization deploys it to |
Precedence runs managed settings highest, then the command line, then project local, shared project, and user. A key in a managed source cannot be overridden by anything a developer writes in their own files, apart from a short list of keys where a stricter value from a lower scope wins.
Managed settings
A managed-settings.json file holds the keys you enforce, in the same JSON shape as settings.json. Anthropic's managed settings guide gives this example, which denies two file reads, turns off bypass mode, and makes Claude Code ignore permission rules from user, project, and local files:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
],
"disableBypassPermissionsMode": "disable"
},
"allowManagedPermissionRulesOnly": true
}
The file goes in the system directory for each platform:
- macOS:
/Library/Application Support/ClaudeCode/managed-settings.json - Linux and WSL:
/etc/claude-code/managed-settings.json - Windows:
C:\Program Files\ClaudeCode\managed-settings.json
An optional managed-settings.d/ directory next to it holds partial files that are merged in alphabetical order, so separate teams can own separate parts of one policy. Confirm the result on one machine with /status inside Claude Code: the Setting sources line should read Enterprise managed settings (file). claude doctor lists entries that were rejected.
Delivery decides whether this is a control or a suggestion. Anthropic lists server-managed settings from the claude.ai admin console, MDM profiles (the com.anthropic.claudecode domain on macOS), and the file-based method above. Server-managed settings reach cloud sessions; a local managed-settings.json does not.
Managed MCP configuration
Claude Code offers five restriction patterns for MCP, documented in managed MCP configuration:
| Pattern | What the user sees | Mechanism |
|---|---|---|
| Disable MCP | Configuration-file servers do not load, with the exceptions below | managed-mcp.json with an empty server map |
| Fixed deployment | The servers the file names, plus the exceptions below | managed-mcp.json listing the servers |
| Approved catalog | The published list, and users pick from it | allowedMcpServers plus allowManagedMcpServersOnly: true |
| Soft allowlist | Your list applies, and users can add to it | allowedMcpServers without allowManagedMcpServersOnly |
| Denylist only | Known-bad servers blocked | deniedMcpServers |
managed-mcp.json sits in the same system directory as managed-settings.json and is a standalone file, so it cannot be delivered through server-managed settings. It is also narrower than "no servers". Anthropic's list of what still loads under exclusive control is the file's own servers, servers provided through managedMcpServers, and in-process servers that the application starting the session registers, such as the VS Code extension's server and the connectors the Desktop app delivers. The file does not reach the connectors a desktop app hands to its local and SSH sessions, and it suppresses the claude.ai connectors Claude Code fetches for itself unless you set allowAllClaudeAiMcps.
The allowlist is a different kind of control. It filters servers that a user, a plugin, or your organization has already added, and Anthropic is explicit that it does only that. Set allowedMcpServers with allowManagedMcpServersOnly: true to make the managed list authoritative, because without that flag allowlists merge across every settings scope and a developer can add entries in their own ~/.claude/settings.json.
Entries match on serverUrl, serverCommand, or serverName, and only the first two carry a security claim. Anthropic's warning on serverName entries is that the name is the label a user assigns, that a user can call any server github, and that a name entry is not a security control. Match the URL for remote servers and the exact command and argument array for stdio ones. serverCommand compares every argument in order and ignores the env block, so an environment variable that changes what node loads sits outside the match.
So an empty managed-mcp.json stops configuration-file servers on that machine, and with a managed-only allowlist an entry a developer adds has to match a managed URL or command before it loads. What the file does not cover is what a desktop or IDE host delivers, which is why the host's own controls belong beside it: connectors off in the organization settings, and the host's configuration managed through device management.
Hooks
Hooks are shell commands that Claude Code runs at fixed points in its loop. A hook declared on PreToolUse runs before a tool call executes and can block it. The hooks key lives in any settings file, including managed-settings.json, so a hook can be deployed and protected the same way a permission rule is.
Anthropic's hooks reference gives this example, which narrows to Bash calls and then to commands matching rm *:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
The script reads the tool input as JSON on stdin and returns a decision:
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0
fi
Exit code 2 blocks the call as well, and onFailure: "block" (Claude Code v2.1.295 and later) closes the gap where a hook that cannot run would otherwise let the action proceed. Hooks are also the cheapest audit source available inside Claude Code: the same script can append the decision to a log before it returns.
What the three mechanisms leave to you
The controls above are real, and each one is per client and per machine. Four things remain.
They are client-side. Anthropic's wording for server-managed settings applies to the family: a client-side control, not a security boundary, and on an unmanaged device a user does not need admin rights to bypass it. MDM protects the file; it does not make the file an enforcement point for what a host application does.
They cover Claude Code. The same laptop usually runs Cursor, the Codex CLI, and GitHub Copilot, each with its own managed configuration, its own allowlist syntax, and its own idea of what a hook is. Four clients means four policies to author and four places to check, and the coverage gaps land wherever the least configured client sits.
They do not inventory the fleet. Managed settings answer what is enforced on a machine. They do not answer which agents and MCP servers are installed across the fleet, which versions are down-level, or which endpoints hold a plaintext token in a config file. That answer needs a collector on each endpoint and a store to compare against.
They do not produce one audit chain. A hook can log locally, and the log belongs to the machine. Joining those records across endpoints, agents, and MCP servers, with the policy decision attached to each action and tamper-evidence an auditor can verify, is a separate job.
Where Alyria Beacon adds a layer
Alyria Beacon is a signed, user-space daemon for macOS, Linux, and Windows that runs alongside these controls. It inventories the AI tooling and MCP server configuration on each endpoint, across Claude Code, Cursor, Codex, Copilot, and the clients nobody mentioned, and keeps that inventory current. Lyra policy is evaluated locally on every MCP call and on inference, so one rule set applies to every client on the machine rather than one rule set per vendor. Enforcement is opt-in and starts in monitor mode. Every decision is signed into one audit chain.
Keep the managed settings and the hooks: they are the vendor's own controls, they are precise, and they cost nothing to run. The layer underneath them is the one that answers questions about the fleet rather than about one machine. /platform/beacon describes the endpoint side, /platform/lyra describes the policy model, and the policy docs show a rule you can test before you deploy it.
A rollout order that works
- Deploy
managed-settings.jsonwith the permission denies you are sure about, and treatdisableBypassPermissionsModeas a default rather than a decision to revisit. - Add
managed-mcp.jsonor an approved catalog, and publish the process for getting a server onto the list. - Add one
PreToolUsehook for the command class you cannot tolerate, such as a destructive shell pattern, and log every decision it makes. - Inventory the fleet read-only. The inventory tells you which of the rules above you would have broken this week, and which other clients need the same treatment.
- Move enforcement from monitor to block per rule, once the false-positive rate is known.
Steps one to three are Anthropic's controls, applied. Step four is where the gaps in this post show up on your own machines.