Courses
Traditional chatbots are configured behind an API, so the worst they can do is hallucinate. But an AI coding agent is placed inside your repo, your terminal, and (often) your cloud credentials, which puts it much closer to privileged developer environments than anything we used to call a chatbot.
The question is how to use Claude Code safely without slowing yourself down. Permissions, MCP controls, and sandboxing with the right amount of tweaking will get you there.
In this article, I’ll walk you through how Claude Code's security model actually works, where you need to pay attention, and the practices that keep it useful without giving it more access than you intended.
If you’re completely new to Claude and Claude Code, enroll in our free Claude Code 101 course to get the basics in one afternoon.
Understanding Claude Code's Security Model
Before tuning a single rule, you need a mental model of what Claude Code actually controls.
There are five moving parts: a permissions system that decides what's allowed, tool access controls that scope individual capabilities, MCP permissions for external integrations, sandboxing for OS-level isolation, and auditability for after-the-fact review. Each one solves a different problem, but they stack.
Permissions system
The permissions system is the static layer.
You declare what Claude can do in settings.json, using three lists: allow, ask, and deny. Rules are evaluated deny first, then ask, then allow, and the first match wins. A deny rule blocks the call even if a broader allow rule would have matched it.
If no rule matches, Claude falls back to the session's defaultMode (more on modes in the next section).
Tool access controls
Permissions attach to tools, not to the agent as a whole.
Claude Code has its own set of built-in tools, for example Bash for shell commands, Read and Edit and Write for filesystem operations, WebFetch for HTTPS requests, WebSearch for queries, and a few others. Each rule names a tool and (optionally) a specifier in parentheses, like Bash(git commit:*) or Read(./.env).
This is what makes least-privilege work. You can allow Bash(npm run:*) for tests without giving Claude full shell access.
MCP permissions
MCP servers extend Claude Code with tools it wasn’t designed to work with.
Each server brings its own set of tools (a GitHub server adds pull request tools, a database server adds query tools, and so on). The permission system covers them too, but with a different syntax - rules use the form mcp__servername__toolname instead of the parenthesized specifier.
The point to remember is that MCP roughly doubles the question because you're not just deciding what Claude can do with your shell, you're deciding what it can do with every external system you've connected to it.
Sandboxing
Sandboxing is the OS-level safety under the Bash tool.
Permission rules tell Claude what it should do. Sandboxing enforces what it can do, by restricting filesystem access and outbound network calls at the operating system level. On macOS it works out of the box via Seatbelt. On Linux and WSL2, you need to install bubblewrap and socat first.
The two layers work similarly but cover different scenarios. Permissions stop Claude from trying something, and sandboxing stops the attempt from succeeding if a prompt injection talks Claude into trying anyway.
Auditability
The last piece is being able to see what happened.
The /permissions command lists every active rule and the settings file each one comes from, so you can answer "why did Claude run that?" Hooks (PreToolUse, PostToolUse, and others) let you log every tool call to your own system. For teams, OpenTelemetry exporters send usage and tool-call data into whatever observability stack you already run.
Claude Code Permissions and Access Control
Most of the safety comes from permission settings, so this is the area you’ll spend the most time tweaking.
File access
Claude can read and edit files in the directory you launched it from, by default.
Reading is controlled by the Read tool, and editing by Edit and Write. Each accepts a path pattern in parentheses, using gitignore-style syntax, for example Read(**/.env) matches every .env file at any depth, and Edit(src/**) matches everything under src/.
A deny on Read covers Claude Code's own file tools (Read, Grep, Glob, LS), but it's just a best-effort attempt. A Python or Node script run through Bash can still open the file, because that read happens through the shell instead of through Claude's Read tool. If a secret matters, combine the Read deny with a Bash deny on cat, head, and tail of those paths.
To extend access beyond the working directory, use additionalDirectories in settings.json. This is how you give Claude access to a shared library outside your repo or a config file in your home directory, without entirely dropping the working-directory boundary.
Command execution
The Bash tool is the one you have to scope most carefully.
A bare Bash rule allows every command. A scoped rule like Bash(npm run:*) allows only matching invocations. The colon-star pattern is what you need to use here, and Claude Code understands shell operators, so a rule like Bash(safe-cmd:*) won't match safe-cmd && rm -rf /.
Some commands run without prompting in every mode, because they're treated as read-only by default. The list covers ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and read-only forms of git. You can't shrink this list by hand, but you can add an ask or deny rule for any of them to override the default.
For anything that isn't pre-approved, Claude prompts in default mode. The prompt shows the exact command and lets you approve it once, approve all future calls matching a pattern, or deny.
Permission modes
Permission rules are static, but permission modes change how unmatched calls behave.
There are five:
-
default: prompts on first use of each tool. -
acceptEdits: auto-approves file edits in the working directory, and still controls shell commands. Useful when you trust the edits but not the shell. -
plan: Claude reads and analyzes, but can't edit files or run commands. The right mode for code review or planning sessions. -
dontAsk: auto-denies anything not explicitly in the allow list. -
bypassPermissions: skips every prompt. Only safe in fully isolated environments like a container or VM.
You can cycle the three main modes with Shift+Tab mid-session, or select one as the default in settings.json:
{
"permissions": {
"defaultMode": "acceptEdits",
"deny": ["Read(**/.env)", "Read(**/.env.*)"]
}
}
For teams, managed settings give you a layer the user can't override. The file follows the same JSON format and is found in a system path:
-
/Library/Application Support/ClaudeCode/managed-settings.jsonon macOS -
/etc/claude-code/managed-settings.jsonon Linux -
C:\ProgramData\ClaudeCode\managed-settings.jsonon Windows
Deny rules in managed settings hold across every project on the machine, which is how you enforce rules like "no one reads .env files" or "no one runs bypassPermissions" across an organization.
The principle behind all of this is least-privilege, the same one you'd apply to any service account.
You should start with the smallest set of permissions that lets the work happen, and widen them only when you hit a wall. Anthropic's own guidance points the same way - review the changes Claude makes, audit your rules with /permissions, and keep project-specific settings checked into version control so the team agrees on what Claude can work on.
Claude Code Sandboxing
The more you allow Claude to run autonomously, the more sense sandboxing makes.
The Bash tool has the broadest exposure since shell commands can read any file you can read and modify anything they have write permission for. Sandboxing forces OS-level boundaries on every Bash command and its children, so Claude can run more freely inside the boundary without you approving each call. Anthropic built this in specifically to support safer autonomous runs.
A detail worth getting is that the sandbox only covers Bash and its child processes. It doesn't restrict the Read, Edit, or Write tools, and those still go through the permission system.
Native sandboxing
The native sandbox is built into Claude Code and turns on with /sandbox.
On macOS, sandboxing uses the built-in Seatbelt framework and there's nothing to install. On Linux and WSL2, you install bubblewrap for filesystem isolation and socat for the network proxy. Native Windows isn't supported, so you run Claude Code inside a WSL2 distribution instead.
The filesystem boundary is easy to understand. Reads work everywhere except denied paths, and writes only work inside the working directory and the additional paths you allow. If you try to write to ~/.bashrc from inside the sandbox you’ll get "Operation not permitted" before Claude even knows it failed.
The network boundary is somewhat different. Outbound traffic routes through a proxy server running outside the sandbox, which checks every request against your allowedDomains list. New domains trigger a permission prompt instead, so you see exactly what Claude is reaching for.
A working configuration looks like this:
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"allowWrite": ["/workspace", "/tmp"],
"denyRead": ["~/.aws", "~/.ssh"]
},
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
}
}
}
In Anthropic's internal usage, sandboxing reduces permission prompts by 84%.
Development containers
Dev containers are the next step up in isolation.
Anthropic has a reference devcontainer for Claude Code that sets up an Ubuntu environment, mounts your repo, and gives the agent a shell to work in. The advantage over native sandboxing is reproducibility, because everyone on the team gets the same environment, with the same tools and the same setup.
But the downside is the overhead.
You add container build, file mounting, and (sometimes) the slower feedback loop when you're using containers. For a single developer, the native sandbox is usually enough. For team or CI use, this setup is worth it.
Docker-based isolation
For long-running autonomous agent sessions, Docker can further increase the sandboxing boundary.
This is what the setup usually looks like:
- Minimal base image: remove package managers and network tools the task doesn't need.
- Non-root user: Claude never runs as root, so it can't modify system files or install global packages.
- Read-only root filesystem: mount the container root as read-only, with only specific output directories writable.
- Egress proxy: route outbound network through a proxy that allows the package registry (npm, PyPI) and denies everything else, so commands like
npm installwork but an arbitrarycurlcommand doesn't. - Resource limits: set limit to CPU, memory, and I/O so a process can't take down the host.
Docker Sandboxes give each sandbox its own microVM with a private Docker daemon. The host daemon can't even see the sandboxes in docker ps. The boundary is closer to a VM than a container, which closes off most of the container-escape paths developers worry about.
Enterprise sandboxing strategies
For organizations, the question is how to layer sandbox techniques, not whether to use them.
Here’s how most of them approach this:
-
Permission rules go in
managed-settings.jsonand can't be overridden by individual developers. -
Native sandboxing runs underneath the permission layer.
-
Dev containers or Docker run underneath that.
-
For the highest-trust scenarios (production access, secrets handling), a dedicated VM with no host filesystem mount is the last layer.
Claude Code on the web is a managed version of the same idea. Each session runs in an Anthropic-managed VM, sensitive credentials like git tokens are placed outside the sandbox in a proxy, and the boundary is enforced by infrastructure.
MCP Security in Claude Code
MCP is the part of Claude Code that grows fastest.
Each MCP server you connect roughly multiplies what Claude can do, but it also multiplies the surface a prompt injection or compromised dependency can reach.
For example:
- A GitHub MCP server gives Claude pull request access.
- A database MCP server gives it your schema and queries.
- A Slack server gives it your channels.
None of that is a problem, but it does mean MCP needs its own governance. Content fetched through an MCP tool (a web page or an API response) can contain injected instructions that Claude executes as if you'd typed them. Also, every server adds a credential and an authentication path that has to be managed separately.
Tool permissions
MCP tools use a different naming convention than built-in tools.
The rule format is mcp__servername__toolname, with no parenthesized specifier. For example, mcp__github__create_pull_request lets Claude call exactly that tool, and a deny on mcp__github__delete_repo blocks the dangerous one. Allow, ask, and deny lists work the same way they do for Bash or Read.
The same precedence rules apply too - deny first, then ask, then allow. A managed-settings deny on mcp__github__delete_* is valid across every project on the machine.
Resource permissions
MCP servers can expose resources along with tools.
A resource is a piece of data the server makes available for Claude to read (a file in a project management server or a row in a database server). Resource access flows through the same trust check as tool calls, and a first-time MCP server connection runs a trust verification step before any of its tools or resources are reachable.
The right default is to treat resources as another tool. If you wouldn't grant the credential, don't grant the resource access.
Approved MCP servers
The Anthropic Directory lists connectors that Anthropic has reviewed against its listing criteria.
For organizations, a good pattern is using an internal allowlist. There are two settings that give you the control:
-
allowedMcpServers: a glob pattern of servers developers can add to their projects (for example,company-*to allow only internally maintained servers). -
deniedMcpServers: a pattern of servers that can't be added, even if the developer tries.
For mandatory servers that should be present in every session, a managed-mcp.json file located in managed settings is the way to ship them. Developers can't remove or modify the entries.
One setting to avoid in shared repos is enableAllProjectMcpServers, which auto-approves every MCP server defined in .mcp.json. That's convenient for solo work and dangerous for anything checked in, because a malicious PR can add a new server to .mcp.json and have it run without a prompt.
Least-privilege tool access
Same principle as Bash permissions, just applied to MCP.
The starting question is what credential each server has. A database MCP server should connect to a read-replica with read-only access, not the primary with write rights. An API MCP server should use a token scoped to the smallest necessary set of endpoints, not a personal token with full org access. And so on.
Subagents are another way to scope MCP access. A subagent definition in .claude/agents/ can declare exactly which tools it has access to (using the mcp:<server>:<tool> syntax), so a "deploy-agent" gets the infrastructure server and a "review-agent" gets only Read, Grep, and Glob. The agent can't call tools it wasn't given.
MCP governance
For a team or organization running Claude Code at scale, MCP needs the same governance shape as any other production integration.
That means a registry of approved servers with named owners, end-to-end audit trails of tool invocations (which MCP tools were called, by whom, with what parameters), and a periodic review of the approval list. OpenTelemetry exporters in Claude Code give you the audit data in a format that drops into whatever observability stack you already run.
For larger organizations, a centralized MCP gateway is the cleanest version of this.
Developers connect to the gateway instead of registering individual servers. The gateway handles authentication, enforces role-based access at the tool level, and emits a single audit trail. It also solves credential sprawl, because one set of credentials at the gateway replaces every developer holding a copy of every API key.
Secrets Management and Sensitive Data
Claude Code can read anything you can read, which makes secrets reachable.
By default, Claude Code can read every file your user account can. That includes .env files in your project, AWS credentials in ~/.aws/, SSH private keys in ~/.ssh/, the GitHub tokens in your shell rc file, and the environment variables in any subprocess Claude creates. That’s perfectly normal and by design.
Keep secrets out of the workspace
The first move is making sure secrets aren't in the directory Claude is reading.
.env files are the most common ones. They are in the project root, get loaded by every dev tool, and contain exactly the values you don't want in a Claude context (database URLs and API keys).
Here are a couple of patterns you can use:
-
Move secrets to a directory outside the working tree, for example
~/.config/myapp/secrets.env, and load them through an environment manager or adirenvsetup that points at the external file. -
For containerized work, keep a
.secrets/folder outside the bind mount so the file is invisible from inside the container. -
Add
Read(**/.env)andRead(**/.env.*)to yourpermissions.denylist, and pair them withBash(cat:*/.env)denies so a shell script can't read what the Read tool can't.
Use a secret manager
For anything beyond demo projects, the right place for secrets is a secret manager.
The pattern is the same regardless of vendor (1Password, AWS Secrets Manager, HashiCorp Vault, Doppler, Infisical). Secrets are placed in the manager. Your shell or runtime gets them on demand and exposes them only to the process that needs them. Claude never sees the actual value.
For Claude Code specifically, this means setting CLAUDE_CODE_SUBPROCESS_ENV_SCRUB to strip Anthropic and cloud provider credentials from subprocesses, or using sandbox.credentials to unset specific variables for sandboxed commands. The first stops Claude from passing your ANTHROPIC_API_KEY into a build script, and the second covers the broader case of any sensitive environment variable making its way into a shell command.
Restrict repository access
The third option is at the repo level.
If a developer doesn't need write access to production-only configuration, their Claude Code session doesn't either. That sounds obvious, but the default for most teams is that developers have broader access than they routinely use, and Claude inherits all of it.
Here are two steps you can take:
-
Split production configuration into a separate repo with stricter access, so the dev environment Claude works in doesn't have the production secrets to begin with.
-
Use scoped tokens for any service Claude interacts with. For example, a GitHub token for code review work doesn't need
repo:delete.
Claude Code Security for Teams
A solo developer can change settings.json however they want. A team can't, because the overall security is only as good as the weakest config on the weakest machine. For team and organization deployments, Claude Code has a separate layer of controls that an admin pushes down and individual users can't override.
Managed settings
Managed settings are the foundation.
The file is located in a system path that requires admin access to write:
-
/Library/Application Support/ClaudeCode/managed-settings.jsonon macOS -
/etc/claude-code/managed-settings.jsonon Linux -
C:\ProgramData\ClaudeCode\managed-settings.jsonon Windows
Settings in this file take precedence over user-level and project-level settings. A deny rule placed here is a deny rule across every project on the machine, and the developer can't remove it by editing their own settings.json. Most orgs distribute the file through MDM (Mobile Device Management) or the same configuration channel they use for other dev tooling.
Here are some settings worth knowing about at the managed layer:
-
permissions.denyrules for sensitive paths and dangerous commands -
defaultModeset todefaultorplan(neverbypassPermissions) -
allowManagedPermissionRulesOnly: trueto lock the permission set -
enableAllProjectMcpServers: falseto require explicit MCP approval -
OpenTelemetry exporter configuration for logging
Shared permission policies
A team that agrees on what Claude can do should version control that agreement.
Project-level settings are located in .claude/settings.json at the repo root. Anything checked in here applies to anyone who runs Claude in that repo. The file is the right place for project-specific allows and denies.
With that in mind, there’s a split between managed and project settings you need to know about:
-
Managed settings contain organization policy (no one runs
bypassPermissions, no one reads.env). -
Project settings contain workflow conventions (this repo's tests run with
npm test;this repo's deploy script is off-limits).
Team governance
For a team rollout, the policy layer needs an owner.
Most teams that run Claude Code at scale end up with a small group, usually security and platform engineering, that owns the managed settings, the MCP allowlist, the hook scripts, and the OpenTelemetry pipeline. The same group reviews exception requests and adjusts policy as new use cases come up.
You should aim to have these pieces in writing:
- Which repos are in scope and which aren't, with risk-tiered modes (a repo dealing with regulated data probably runs Claude in
planmode, while a marketing site repo can run inacceptEdits). - Who can grant exceptions and how those are tracked.
- A review cadence (quarterly is common) where the team revisits permission rules, MCP servers, and incident data.
Audit logging
Claude Code emits OpenTelemetry events for every tool decision, MCP server connection, permission mode change, and API request. No data flows until an admin configures the OTLP endpoint in managed settings.
Here is a minimal managed settings block for telemetry:
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.internal:4317"
}
}
By default, prompt content and tool parameters are excluded from the export, so the events you collect are metadata, not the full conversation. To include prompt text, you set OTEL_LOG_USER_PROMPTS=1. To include tool arguments (which is what you usually want for audit), you set OTEL_LOG_TOOL_DETAILS=1. Both decisions have privacy implications, so most teams treat them as deliberate policy choices and configure their telemetry backend to filter or redact before storage.
Usage monitoring
The same OpenTelemetry stream that’s behind audit is also behind usage monitoring.
Claude Code exports metrics for token usage, cost per request, session counts, and tool decision rates. When aggregated, these let you know which teams are getting the most value, which workflows produce the most rejections, and which models are driving cost. Backends like Datadog, Honeycomb, SigNoz, Elastic, and Splunk all ingest the standard OTLP format.
A spike in permission_decision events with decision=deny could mean Claude is trying too much, but it could also mean the team's allow rules are too tight.
Common Claude Code Security Mistakes
A small set of misconfigurations shows up in most Claude Code incidents. I’ll now show you what these are and what to do about them.
Overly broad permissions
The fastest way to dull the permission system is to allow too much.
Per-command prompts add friction, and the easy fix is a broad Bash(*) allow or a defaultMode: bypassPermissions setting. Both of these undo most of what the permission system does.
You should scope allow rules to specific tools and commands you actually use (for example Bash(npm test:*) and Bash(git status)), and let everything else fall back to a prompt. You get more prompts at first but within a few sessions, you've allow-listed the commands you use and the prompts mostly stop.
Unrestricted MCP access
The second mistake is connecting MCP servers without checking what credentials they're using or what they can reach.
This usually happens because someone enables enableAllProjectMcpServers, connects a few servers from the public MCP directory, and never comes back to review them. By the time a server with a weak credential leaks something sensitive, the connection is far enough back in the configuration that no one remembers approving it.
The fix is the same as for permissions. Use an explicit allowlist via allowedMcpServers, an internal managed-mcp.json for the servers everyone needs, and a review cadence on the list.
No sandboxing
If sandboxing is off, the permission system is the only thing between Claude and your filesystem.
That's fine for short interactive sessions where you're approving each command anyway. It isn't fine for autonomous runs, for sessions where you've broadened allow rules, or for any work touching code from outside sources.
/sandbox turns it on. If the dependencies aren't installed, the menu shows you which ones to install for your platform. Once on, the permission prompts drop and the OS catches the cases your allow rules don't.
Blindly accepting changes
acceptEdits is both convenient and dangerous.
When Claude is rewriting a function and you’re monitoring it, auto-accept is fine. When Claude is iterating across 30 files over an hour, you tend to stop reading the diffs and start trusting the agent. That's where the problems can occur.
Here are two habits you should do:
-
Always commit before letting Claude run autonomously, so the rollback path is one
git resetaway. -
Review the diff before each Claude-authored commit, not the cumulative diff at the end of a session.
Ignoring audit trails
A team running Claude Code without telemetry has no way to answer the question "which session did that?" The events accumulate locally on each machine and stay there. The first time you need an audit trail is also the worst time to discover you didn't configure one.
The minimum useful baseline is exporting tool_decision, permission_decision, and api_request events to whatever observability stack the team already runs. From there, you build dashboards and alerts as use cases come up.
Conclusion
A worst-case scenario for a chatbot is a bad answer. But for a coding agent, it’s a shell command running against production with your credentials.
That's why the three pillars matter:
- Permissions decide what Claude is allowed to do
- MCP controls decide which external systems it can reach
- Sandboxing decides what happens when the first two aren't enough
Each one covers a failure mode the others don't. Together, they define the actual boundary Claude works inside.
If you’re looking to get certified in generative AI, here are the comparisons, top courses, preparation tips, and FAQs for the Best Generative AI Certifications in 2026.
FAQs
What is Claude Code's security model based on?
Claude Code's security is built on three layers. Permissions decide which tools and commands Claude can run, MCP controls scope which external systems it can reach, and sandboxing forces filesystem and network boundaries at the operating system level. Each layer covers a failure mode the others don't.
Is Claude Code safe to use for production work?
It can be, but the defaults aren't configured for it. A production-safe setup involves scoped permission rules, sandboxing enabled, MCP servers on an allowlist, and secrets kept out of the working directory. Teams should also configure OpenTelemetry to get an audit trail before any Claude Code session works on production code.
How is securing Claude Code different from securing a regular chatbot?
A chatbot's worst case is a bad answer. Claude Code can read files, run shell commands, and call external tools, so its worst case is code that actually runs against your systems. The question now is “what can it do?” instead of “what can it say,” so permission rules, sandboxing, and MCP governance carry most of the weight.
How do I prevent Claude Code from reading .env files or other secrets?
Add Read(**/.env) and Read(**/.env.*) to your permissions.deny list, and pair them with Bash(cat:*/.env) denies so a shell command can't read what the Read tool can't. For anything sensitive, move the file outside the working directory (for example into ~/.config/) and load it through a secret manager or an environment tool like direnv.
What's the difference between Claude Code's permission modes?
There are five: default prompts on first use of each tool, acceptEdits auto-approves file edits but still gates shell commands, plan lets Claude read and analyze but blocks edits and commands, dontAsk auto-denies anything not explicitly allowed, and bypassPermissions skips every prompt (only safe in an isolated environment like a container or VM). Most interactive work runs in default or acceptEdits, and headless or autonomous runs should use dontAsk with a scoped allow list.

