AI Security Posture · Reference ← Back to the posture review
Phase 3 · Defend · v1.0 · 14 May 2026

Locking Down Claude Code

A practical security-posture reference for organisations deploying Claude Code across staff or contractor laptops. What the platform exposes, what an enterprise can actually control, what it can't, and a worked managed-settings.json you can adapt.

Aligned with the three-phase service model. Discover → Govern → Defend. This document is a Phase 3 artefact, scoped to the Claude Code product specifically. Equivalent docs for Cursor, GitHub Copilot CLI, Gemini CLI and Aider follow the same shape but use different config mechanisms.
Snapshot taken 2026-05. Claude Code ships rapidly; verify any specific flag against the current canonical reference at code.claude.com/docs before deploying.

Why this matters

Claude Code is a developer agent with substantial capability on the user's machine. By default it can:

The threat model for an enterprise deploying it is the same as for any privileged developer tool, plus the new AI-specific concerns of prompt-borne data exfiltration and supply-chain risk via the plugin/MCP ecosystem.

What you're trying to prevent

RiskConcrete example
Prompt-borne data exfiltrationHigh Developer pastes customer PII or source code into a prompt; it goes to Anthropic and (optionally) any wired-up MCP server.
Unauthorised tool executionHigh Agent runs rm -rf, curl | sh, git reset --hard or a destructive migration without the user reading the permission prompt.
Permission-prompt bypassHigh User passes --dangerously-skip-permissions or has it baked into a wrapper script.
Plugin / marketplace supply chainMedium User installs a community plugin or marketplace that ships a malicious skill, hook, or MCP server.
MCP server abuseMedium User wires up an MCP server that reads/writes far more than the immediate task needs (e.g. full Gmail mailbox).
Hook abuseMedium A user-installed hook silently posts prompt content to an attacker-controlled URL.
Cross-account confusionMedium User signs in with a personal Claude account instead of the corporate one — usage and data leave the corporate boundary.
Session data on diskCompliance Months of conversation transcripts (with embedded secrets) sit unencrypted in the user's home directory; or get auto-uploaded to claude.ai.
Stale-version compliance gapCompliance Old Claude Code versions miss security fixes; the auto-updater pulls a release the security team hasn't validated.

The rest of this document maps each of these to the specific settings, hooks, and deployment patterns that mitigate them.

The settings hierarchy

Claude Code resolves settings in strict order — managed (highest priority) → project policy → user → project → local. Higher tiers can both override values and lock down whether lower tiers may set them at all.

File locations

TierPathWritable by
Managed (enterprise) macOS  /Library/Application Support/ClaudeCode/managed-settings.json
Linux/WSL  /etc/claude-code/managed-settings.json
Windows  C:\Program Files\ClaudeCode\managed-settings.json
Admin / MDM
Managed drop-ins .../managed-settings.d/*.json — fragments merge alphabetically Admin / MDM
User ~/.claude/settings.json The user
Project (shared) .claude/settings.json in the repo Anyone who can commit
Project (local) .claude/settings.local.json (gitignored) The user

Windows can additionally drive managed settings from HKLM\SOFTWARE\Policies\ClaudeCode (Group Policy). WSL can be made to inherit Windows policy by setting wslInheritsWindowsSettings: true in both an admin-only Windows source and HKCU (double opt-in).

What only managed settings can do

Several settings are policy-only: they're respected only when set in managed settings, and they constrain everything below them. These are the load-bearing controls for an enterprise deployment:

If you only remember one thing: managed settings + MDM is the layer that makes the rest of this document possible. Without it you can recommend, but you cannot enforce.

The lockdown toolkit, by risk

Each subsection below lists the settings and patterns that address one of the threats from §1. Lift the relevant snippets into your managed-settings.json.

3APrevent data exfiltration via prompts

The single highest-value control is a UserPromptSubmit hook that runs DLP on every prompt before it leaves the user's machine. The hook fires synchronously, has the prompt text on stdin, and can block submission by exiting with code 2.

DLP UserPromptSubmit hook managed-settings.json fragment
{
  "allowManagedHooksOnly": true,
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/usr/local/bin/cc-dlp-check",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

A minimal cc-dlp-check reads the prompt JSON from stdin, scans for company PII patterns (customer IDs, source-of-record IDs, NHS numbers, source-code markers, AWS keys, etc.) and exits non-zero with a clear message if it finds any. Tune patterns to match the org's data classes — generic DLP regex packs miss most useful signals.

The same pattern works on PreToolUse for Bash, Edit, Write, WebFetch, and on each MCP server — so an MCP tool call carrying sensitive data can be blocked before it leaves the box.

Network egress

Claude Code's bundled sandbox supports per-domain network policy. Keep the allowlist tight — Anthropic's API, your code host, your registry, and nothing else — so that even a compromised hook or MCP server can't reach an attacker-controlled endpoint.

Sandbox network allowlist managed-settings.json fragment
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "network": {
      "allowedDomains": [
        "api.anthropic.com",
        "*.api.anthropic.com",
        "github.com",
        "*.github.com",
        "registry.npmjs.org",
        "pypi.org",
        "*.pythonhosted.org"
      ],
      "allowManagedDomainsOnly": true,
      "deniedDomains": [
        "*.pastebin.com",
        "*.ngrok.io",
        "*.serveo.net",
        "transfer.sh"
      ]
    }
  }
}

allowManagedDomainsOnly: true is the lockdown — without it, users or plugins could append to the allowlist from a lower-priority settings file. deniedDomains merges from all sources regardless, so an end user can block more for themselves.

Session data residency

Local transcripts live at ~/.claude/projects/<sanitised-cwd>/<session>.jsonl and contain the full conversation, including any sensitive content typed in. Default retention is 30 days.

Session retention & off-device push controls managed-settings.json fragment
{
  "cleanupPeriodDays": 7,
  "autoUploadSessions": false,
  "agentPushNotifEnabled": false,
  "inputNeededNotifEnabled": false,
  "channelsEnabled": false
}

For a hard "no transcripts on disk", users can launch with claude --no-session-persistence. There is no global setting to enforce this — consider wrapping the binary or a SessionStart hook that exits non-zero if persistence is enabled.

3BPrevent malicious or risky tool execution

The default permission system is good — by design it asks before every destructive operation. Two real-world attack paths defeat it:

  1. The user passes --dangerously-skip-permissions (or accepts the bypass dialog once and forgets).
  2. A user-level allow rule (e.g. Bash(*)) is too broad.

Lock both down in managed settings:

Permission lockdown & deny rules managed-settings.json fragment
{
  "permissions": {
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable",
    "disableAutoMode": "disable",
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)",
      "Bash(curl * | sh)",
      "Bash(curl * | bash)",
      "Bash(wget * | sh)",
      "Bash(git reset --hard *)",
      "Bash(git push --force*)",
      "Bash(git push -f *)",
      "Bash(docker run *--privileged*)",
      "Bash(chmod 777 *)",
      "WebFetch(domain:pastebin.com)",
      "WebFetch(domain:ngrok.io)",
      "Read(/etc/shadow)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/credentials)",
      "Read(~/.gnupg/**)"
    ],
    "allow": [
      "Read(./**)",
      "Edit(./**)"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "skipDangerousModePermissionPrompt": false,
  "skipAutoPermissionPrompt": false
}

Sandbox-level enforcement

For high-security deployments, run the platform sandbox so that even an approved tool call can't reach outside its lane:

Filesystem sandbox managed-settings.json fragment
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "autoAllowBashIfSandboxed": true,
    "filesystem": {
      "denyRead": [
        "/Users/*/Library/Application Support/Google/Chrome",
        "/Users/*/Library/Application Support/Slack",
        "/Users/*/.ssh",
        "/Users/*/.aws",
        "/Users/*/.gnupg",
        "/Users/*/.kube"
      ],
      "denyWrite": [
        "/etc/**",
        "/Library/LaunchDaemons/**",
        "/Users/*/Library/LaunchAgents/**"
      ]
    }
  }
}

3CPrevent supply-chain attacks (plugins, MCP, skills, marketplaces)

Plugins, skills, agents, hooks, MCP servers and marketplaces are all arbitrary-code execution vectors. The default install lets users add any of them at will. For enterprise:

Plugin / marketplace / MCP lockdown managed-settings.json fragment
{
  "strictPluginOnlyCustomization": ["skills", "agents", "hooks", "mcp"],

  "strictKnownMarketplaces": [
    { "source": "github", "repo": "your-org/claude-marketplace" },
    { "source": "hostPattern", "hostPattern": "^github\\.your-org\\.com$" }
  ],
  "extraKnownMarketplaces": {
    "internal": {
      "source": { "source": "github", "repo": "your-org/claude-marketplace" }
    }
  },

  "allowedMcpServers": [
    { "serverName": "internal-jira", "serverUrl": "https://mcp.your-org.com/*" },
    { "serverName": "github-readonly", "serverCommand": ["npx", "-y", "@modelcontextprotocol/server-github"] }
  ],
  "deniedMcpServers": [
    { "serverName": "filesystem-unrestricted" }
  ],
  "allowManagedMcpServersOnly": true,

  "disableSkillShellExecution": true,
  "pluginTrustMessage": "Plugins are vetted by IT Security before approval. Installing a plugin not from your-org/claude-marketplace is a violation of company AI Usage Policy."
}

Vetting bar. An MCP server should clear the same bar as a new SaaS vendor: data-flow review, OAuth scope review, DPIA where needed. "Just install it locally" is a vendor-onboarding bypass and worth flagging in the governance review process from SERVICE.md Phase 2.

3DPrevent unauthorised auth / account use

Login & model enforcement managed-settings.json fragment
{
  "forceLoginMethod": "claudeai",
  "forceLoginOrgUUID": [
    "11111111-2222-3333-4444-555555555555"
  ],
  "availableModels": [
    "opus-4-7",
    "sonnet-4-6",
    "haiku-4-5"
  ]
}

For Bedrock/Vertex/Foundry-routed deployments, set the relevant environment variables in the managed env block so users can't accidentally bypass the gateway.

3EMaintain observability

You can't respond to what you can't see. A baseline managed audit pipeline:

SIEM forwarding via HTTP hooks managed-settings.json fragment
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "https://siem.your-org.com/claude-code/bash",
            "timeout": 3,
            "headers": { "Authorization": "Bearer $SIEM_TOKEN" },
            "allowedEnvVars": ["SIEM_TOKEN"]
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://siem.your-org.com/claude-code/prompt",
            "timeout": 3,
            "async": true
          }
        ]
      }
    ]
  },
  "allowedHttpHookUrls": ["https://siem.your-org.com/*"],
  "httpHookAllowedEnvVars": ["SIEM_TOKEN"]
}

3FMaintain version integrity

Version pinning managed-settings.json fragment
{
  "minimumVersion": "2.1.75",
  "autoUpdatesChannel": "stable",
  "env": { "DISABLE_AUTOUPDATER": "0" }
}

3GHook safety

Hooks are arbitrary code execution. They're the most powerful control you have and the most powerful vector if mishandled.

Hook-source & HTTP-target lockdown managed-settings.json fragment
{
  "allowManagedHooksOnly": true,
  "allowedHttpHookUrls": ["https://siem.your-org.com/*"],
  "httpHookAllowedEnvVars": ["SIEM_TOKEN"]
}

Worked example: a high-security managed-settings.json

The example below combines the snippets above into a single deployable file. Adapt the marketplace/MCP names, SIEM URL, login org UUID and denylists for your org. Treat it as code: version-control it, code-review changes, roll it out via your normal MDM change process.

Complete managed-settings.json ~100 lines · drop in to /Library/Application Support/ClaudeCode/
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",

  "forceLoginMethod": "claudeai",
  "forceLoginOrgUUID": ["YOUR-ORG-UUID-HERE"],
  "forceRemoteSettingsRefresh": true,

  "availableModels": ["opus-4-7", "sonnet-4-6", "haiku-4-5"],

  "minimumVersion": "2.1.75",
  "autoUpdatesChannel": "stable",

  "cleanupPeriodDays": 7,
  "autoUploadSessions": false,
  "agentPushNotifEnabled": false,
  "inputNeededNotifEnabled": false,
  "channelsEnabled": false,

  "permissions": {
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable",
    "disableAutoMode": "disable",
    "deny": [
      "Bash(rm -rf *)", "Bash(sudo *)",
      "Bash(curl * | sh)", "Bash(curl * | bash)",
      "Bash(wget * | sh)",
      "Bash(git reset --hard *)",
      "Bash(git push --force*)", "Bash(git push -f *)",
      "Bash(docker run *--privileged*)",
      "Read(~/.ssh/**)", "Read(~/.aws/credentials)",
      "Read(~/.aws/config)", "Read(~/.gnupg/**)",
      "Read(~/.kube/**)", "Read(/etc/shadow)",
      "WebFetch(domain:pastebin.com)",
      "WebFetch(domain:ngrok.io)",
      "WebFetch(domain:transfer.sh)"
    ]
  },
  "allowManagedPermissionRulesOnly": true,

  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": {
      "allowedDomains": [
        "api.anthropic.com", "*.api.anthropic.com",
        "github.com", "*.github.com",
        "registry.npmjs.org",
        "pypi.org", "*.pythonhosted.org"
      ],
      "allowManagedDomainsOnly": true,
      "deniedDomains": [
        "*.pastebin.com", "*.ngrok.io",
        "*.serveo.net", "transfer.sh"
      ]
    },
    "filesystem": {
      "denyRead": [
        "/Users/*/.ssh", "/Users/*/.aws",
        "/Users/*/.gnupg", "/Users/*/.kube"
      ]
    }
  },

  "strictPluginOnlyCustomization": ["skills", "agents", "hooks", "mcp"],
  "strictKnownMarketplaces": [
    { "source": "github", "repo": "your-org/claude-marketplace" }
  ],
  "extraKnownMarketplaces": {
    "internal": {
      "source": { "source": "github", "repo": "your-org/claude-marketplace" }
    }
  },

  "allowedMcpServers": [
    { "serverName": "internal-jira", "serverUrl": "https://mcp.your-org.com/*" }
  ],
  "allowManagedMcpServersOnly": true,
  "deniedMcpServers": [
    { "serverName": "filesystem-unrestricted" }
  ],

  "disableSkillShellExecution": true,
  "pluginTrustMessage": "Plugins must come from your-org/claude-marketplace. Installing third-party plugins is a violation of company AI Usage Policy.",

  "hooks": {
    "UserPromptSubmit": [{
      "hooks": [
        { "type": "command", "command": "/usr/local/bin/cc-dlp-check", "timeout": 5 },
        { "type": "http", "url": "https://siem.your-org.com/claude-code/prompt", "async": true, "timeout": 3 }
      ]
    }],
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [
        { "type": "http", "url": "https://siem.your-org.com/claude-code/bash", "async": true, "timeout": 3 }
      ]
    }]
  },
  "allowManagedHooksOnly": true,
  "allowedHttpHookUrls": ["https://siem.your-org.com/*"],
  "httpHookAllowedEnvVars": ["SIEM_TOKEN"]
}

Deploying via MDM

The managed-settings file is just a JSON file on disk at a known path. MDM deployment is straightforward — what matters is filesystem ACLs, so the user can't overwrite it.

macOSJamf · Kandji · Mosyle · Intune

Linux / WSLAnsible · Chef · Puppet

WindowsIntune · Group Policy · SCCM

Verifying the policy is active

Users can run /status and the managed-settings-loaded indicator shows in the diagnostic output. From an admin's perspective, forceRemoteSettingsRefresh: true plus a startup-blocking check is the surest way to confirm policy is being applied — the binary refuses to start without it.

Things you cannot lock down

Be honest with stakeholders about the gaps. None of these are showstoppers, but they shape the threat model.

Posture checklist

A short version for an engagement readout, or as a Q-set for the AI security posture quiz. Hover to tick.

Related controls outside Claude Code

This document covers controls inside the Claude Code product. The full defensive picture includes:

The 15-domain posture framework maps cleanly onto these — see the posture review for the question set this document plugs into.

Need this rolled out across your org?

We help organisations move from ad-hoc AI usage to a defensible, audit-ready posture — managed-settings deployment via your existing MDM, DLP hook tuning, marketplace governance, incident playbooks, and the cross-tool view across Claude Code, Cursor, Copilot, Gemini CLI and Aider.