The Claude Agent SDK is Anthropic's library for running Claude Code's agent loop, built-in tools, permissions, hooks and sessions inside your own Python or TypeScript application. It was renamed from the Claude Code SDK in September 2025. To run it safely, pin tools with allow and deny rules, add hooks, and isolate it.
This post goes one level deeper than our earlier pieces on Claude Code subagents, computer-use agents and how to sandbox them and AI agent tool calling security. Here we stay on the SDK itself. Every statement about the SDK, Claude Code or Anthropic below comes from Anthropic's own documentation, its GitHub repositories or anthropic.com, linked in the Sources section. Version numbers in the docs move quickly, so check the linked pages before you build.
Key takeaways
- The Agent SDK runs the Claude Code binary as a subprocess, giving Python (3.10+) and TypeScript (Node.js 18+) apps the same tools, agent loop and context management as Claude Code.
- Anthropic frames the loop as gather context, take action, verify work, repeat. Each turn, Claude requests tool calls, the SDK runs them and returns the results, until Claude answers with no tool calls.
- Permission checks run in a fixed order: hooks, deny rules, ask rules, permission mode, allow rules, then your
canUseToolcallback. Knowing that order is the single most useful thing for writing a safe configuration. allowed_toolsdoes not restrictbypassPermissions. In that mode every unlisted tool is approved too. Usedisallowed_tools, deny rules or hooks to block things.- A
PreToolUsehook runs before every other check, and a hook deny applies even inbypassPermissionsmode. That makes hooks the right place for policy you must never skip. - Anthropic's sandbox puts an operating system boundary around shell commands only. File tools, web tools, hooks and local MCP servers run outside it, so production agents also need a container, gVisor or a VM around the whole process.
- Anthropic's secure deployment guide recommends injecting credentials through a proxy outside the agent's boundary, read-only code mounts, dropped Linux capabilities and no direct network.
- The agent has its own dependency tree (SDK, bundled binary, MCP servers, skills). Pin, lock and inventory it like any production service.
What is the Claude Agent SDK?
Anthropic's overview describes an agent as "an application that completes a task by planning its own steps and calling tools that read files, run commands, or edit code." The Agent SDK gives you the same tools, agent loop and context management that power Claude Code, programmable in Python and TypeScript. Anthropic's own short description is "Build production AI agents with Claude Code as a library."
Under the hood, the hosting guide explains that when your code calls query(), the SDK spawns a separate claude CLI process and talks to it over stdio. That subprocess owns the shell, the working directory and the JSONL session transcripts on local disk. Both SDKs bundle a native Claude Code binary, so most installs need no separate Claude Code install. One agent session maps to one subprocess, so N concurrent sessions means N subprocesses.
Anthropic's overview places the SDK next to three other ways of building with Claude:
| You want to | Use | What you get (per Anthropic's overview) |
|---|---|---|
| Embed Claude Code's agent in your own Python or TypeScript app, in a process you operate | Agent SDK | A library that runs the Claude Code binary, with built-in tools, permissions, sessions and hooks |
| Work interactively or run one-off tasks from a terminal | Claude Code CLI | The terminal interface, built for daily use |
| Call the Claude API directly | Client SDK | Direct API access; you write the tool loop yourself or use the beta tool runner |
| Have Anthropic host the agent | Managed Agents | A hosted harness, with sessions in an Anthropic-managed sandbox or a self-hosted sandbox |
Anthropic lists the SDK's capabilities as built-in tools, hooks, subagents, MCP, permissions, sessions, skills, commands, memory and plugins. Use is governed by Anthropic's Commercial Terms of Service, and unless previously approved, third party products built on the SDK may not offer claude.ai login; the quickstart uses API keys.
How did it relate to the Claude Code SDK?
It is the same harness under a new name. In "Building agents with the Claude Agent SDK", published on 29 September 2025, Anthropic wrote that the agent harness powering Claude Code (the Claude Code SDK) "can power many other types of agents, too", and that it was renaming the Claude Code SDK to the Claude Agent SDK to reflect that broader vision. The Python SDK changelog marks the change at version 0.1.0: "Introducing the Claude Agent SDK! The Claude Code SDK has been renamed to better reflect its capabilities for building AI agents across all domains, not just coding." The TypeScript repository README says simply that "The Claude Code SDK is now the Claude Agent SDK."
Anthropic's migration guide lists what changed:
| Aspect | Old | New |
|---|---|---|
| TypeScript or JavaScript package | @anthropic-ai/claude-code | @anthropic-ai/claude-agent-sdk |
| Python package | claude-code-sdk | claude-agent-sdk |
| Python import and options type | claude_code_sdk, ClaudeCodeOptions | claude_agent_sdk, ClaudeAgentOptions |
| Default system prompt | Claude Code's system prompt | A minimal system prompt; request the claude_code preset explicitly to get the old behavior |
One default matters for security. Omitting settingSources loads user, project and local settings, matching the CLI: ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, CLAUDE.md files and custom commands. Anthropic calls isolation "especially important for CI/CD pipelines, deployed applications, test environments, and multi-tenant systems"; pass an empty list to get it. Python SDK 0.1.59 and earlier treated an empty list as omitted, so upgrade first.
Which languages does it support?
Python and TypeScript. The quickstart lists Node.js 18 or later or Python 3.10 or later as prerequisites, with npm install @anthropic-ai/claude-agent-sdk and pip install claude-agent-sdk (or uv add claude-agent-sdk) as the install commands. For any other language, Anthropic's overview says you can drive the same loop by running the CLI as a subprocess with the -p flag and JSON output. Besides the Claude API, the quickstart documents environment variables for Amazon Bedrock, Google Cloud's Agent Platform and Microsoft Foundry.
How does the agent loop work?
Anthropic gives two descriptions of the loop, one conceptual and one mechanical, and you need both.
Anthropic's framing: gather context, take action, verify work
In the September 2025 article, Anthropic wrote that in Claude Code, Claude "often operates in a specific feedback loop": gather context, take action, verify work, repeat, and suggested it as a way to think about any agent and the capabilities it should be given. The article walks through each stage:
- Gather context. Agentic search over the file system comes first, using shell tools like
grepandtail, so "the folder and file structure of an agent becomes a form of context engineering." Subagents work in parallel in isolated context windows, and compaction summarizes old messages near the context limit. - Take action. Tools are "the primary building blocks of execution", so design them as the main actions you want. Bash, code generation and MCP integrations cover the rest.
- Verify work. Anthropic recommends defined rules (linting is its example), visual feedback such as screenshots, and a model as judge, which it cautions is not very dependable and can add heavy latency.
The security reading of this framing is direct. Every stage is a place where untrusted content can enter. Gathering context reads files and web pages you did not write. Taking action executes commands. Verifying work often means running code. A safe configuration treats all three as part of the attack surface.
The mechanics: turns, messages and results
The SDK's agent loop page describes what actually happens when you call query():
- Receive prompt. Claude receives your prompt along with the system prompt, tool definitions and conversation history. The SDK yields a
SystemMessagewith subtypeinitcarrying session metadata. - Evaluate and respond. Claude decides how to proceed and may respond with text, tool calls or both. The SDK yields one
AssistantMessageper content block. - Execute tools. The SDK runs each requested tool, and the results feed back to Claude. Hooks can intercept, modify or block calls before they run.
- Repeat. Steps 2 and 3 cycle. Each full cycle is one turn, and turns continue until Claude produces a response with no tool calls.
- Return result. The SDK yields a final
AssistantMessageand then aResultMessagewith the final text, token usage, cost and session ID.
Anthropic's worked example is the prompt "Fix the failing tests in auth.ts": turn one runs npm test through Bash, turn two reads two files, turn three edits auth.ts and reruns the tests, and a final text-only turn reports success. That is four turns, three with tool calls.
The core message types are SystemMessage, AssistantMessage, UserMessage (tool results), StreamEvent (with partial messages on) and ResultMessage, whose subtype tells success from a limit. A few system events can arrive after the result, so iterate the stream to completion.
Turns, budget and effort
Two options cap the loop, and both default to no limit:
| Option (Python / TypeScript) | Controls | When the limit is hit |
|---|---|---|
max_turns / maxTurns | Tool-use round trips only | ResultMessage with subtype error_max_turns |
max_budget_usd / maxBudgetUsd | Estimated spend at which the loop stops | ResultMessage with subtype error_max_budget_usd |
Anthropic calls a budget "a good default for production agents." The budget includes subagent spend, and because cost is computed after each response arrives, the response that crosses the cap still completes, so spend can overshoot by one response plus whatever running subagents spend before they stop. Leave headroom. A separate effort option trades reasoning depth against tokens and latency.
Context window and compaction
The context window does not reset between turns; everything accumulates, and stable content is prompt cached. Near the limit, the SDK compacts older history into a summary and emits a compact_boundary message. Anthropic notes that compaction "replaces older messages with a summary, so specific instructions from early in the conversation may not be preserved." Persistent rules belong in CLAUDE.md, which is re-injected on every request, rather than in the first prompt. For security this means you should never rely on a sentence in the opening prompt ("never touch production") as your only control. Put hard rules in hooks and permission rules, which do not get summarized away. A PreCompact hook can archive the full transcript before summarizing if you need it for audit.
What built-in tools does the SDK ship?
The agent loop page lists the tools the SDK shares with Claude Code:
| Category | Tools | What they do |
|---|---|---|
| File operations | Read, Edit, Write | Read, modify and create files |
| Search | Glob, Grep | Find files by pattern, search content with regex |
| Execution | Bash | Run shell commands, scripts, git operations |
| Web | WebSearch, WebFetch | Search the web, fetch and parse pages |
| Discovery | ToolSearch | Find and load tools on demand instead of preloading all of them |
| Orchestration | Agent, Skill, AskUserQuestion, TaskCreate, TaskUpdate | Spawn subagents, invoke skills, ask the user, track tasks |
You can add MCP servers, custom tools and project skills. Read-only tools can run concurrently and state-changing tools run sequentially; custom tools are sequential unless you set readOnlyHint, so set it only on tools that truly change nothing. When a tool is denied, Claude receives a rejection message as the tool result and typically tries a different approach or reports that it could not proceed. A denial is information to the model, not the end of the session, which is why a determined task can go looking for a second route (Bash instead of Write, for example). Your rules need to close the second route too.
The Python README adds one more point: by default Claude has access to the full Claude Code toolset, and allowed_tools "does not remove tools from Claude's toolset." To take a tool away, use disallowed_tools.
How do permissions work?
The evaluation order
Anthropic's permissions page states that when Claude requests a tool, the SDK checks permissions in this order:
| Step | Check | What it can do |
|---|---|---|
| 1 | Hooks | Deny outright or pass the call on. A hook allow does not skip the deny and ask rules below |
| 2 | Deny rules (disallowed_tools and settings files) | Block the call, even in bypassPermissions mode |
| 3 | Ask rules (settings files) | Send the call to your canUseTool callback, even in bypassPermissions mode |
| 4 | Permission mode | Approve according to the active mode, or fall through |
| 5 | Allow rules (allowed_tools and settings files) | Approve matching calls |
| 6 | canUseTool callback | Decide anything not yet resolved; in dontAsk mode this step is skipped and the call is denied |
Edges worth memorizing: bare-name deny rules like Bash remove the tool from Claude's context before evaluation, while scoped ones like Bash(rm *) are checked at step 2. No hook or allow rule can approve an rm of a path Claude Code recognizes as critical, such as rm -rf ~, though Anthropic notes that check "can miss some commands." AskUserQuestion, MCP tools marked as requiring user interaction and connector tools your organization set to ask always reach your callback (or are denied in dontAsk).
Allow and deny rules
allowed_tools and disallowed_tools (TypeScript: allowedTools and disallowedTools) add entries to the allow and deny lists. The permissions page gives these behaviors:
| Option | Effect |
|---|---|
allowed_tools=["Read", "Grep"] | Read and Grep are auto-approved. Other tools still exist, and calls that need approval fall through to the mode and canUseTool |
disallowed_tools=["Bash"] | The Bash tool definition is removed. Claude does not see it and cannot attempt it |
disallowed_tools=["Bash(rm *)"] | Bash stays. Calls matching rm * as written are denied in every mode; other Bash calls, including /bin/rm, fall through |
disallowed_tools=["*"] | Every tool definition is removed. mcp__* matches every MCP tool across all servers |
That third row deserves a second look. A pattern deny on Bash matches the command as written, and Anthropic's own example notes that /bin/rm is not caught by Bash(rm *). Pattern rules on shell commands are useful for intent, but they are not a containment boundary. If a task must not delete files, remove Bash or run it inside the sandbox described below.
Allow globs work only after a literal mcp__<server>__ prefix; an unanchored * in the allow list is ignored. Edit(path) rules govern every built-in tool that writes files, including Write and NotebookEdit. Path anchoring is subtle: //path is absolute, while a single leading slash in allowed_tools or disallowed_tools anchors at the working directory. So Edit(//secrets/**) blocks /secrets on disk, and Edit(/secrets/**) does not. Rules can also live in .claude/settings.json, read when the project setting source is on.
Permission modes
The SDK supports six modes:
| Mode | Behavior per Anthropic's docs |
|---|---|
default | No mode-based auto-approvals. Calls that need approval and match no allow rule go to canUseTool; with no callback, they are denied |
dontAsk | Never prompts. Pre-approved tools and calls that need no approval run; anything that would prompt is denied |
acceptEdits | Auto-approves file edits and filesystem commands (mkdir, touch, rm, rmdir, mv, cp, sed) inside the working directory or added directories |
plan | Claude explores and plans without editing source files; edits are never auto-approved and go to canUseTool |
auto | A model classifier reviews actions such as shell commands and network requests and allows or blocks each one |
bypassPermissions | Runs tools without prompts, except explicit ask rules, organization ask connector tools, tools that require user interaction and recognized critical-path removals |
Anthropic attaches strong warnings to bypassPermissions: "Use with extreme caution. Claude has full system access in this mode." On Linux and macOS, Claude Code refuses to start in this mode as root or under sudo outside a recognized sandbox. Most importantly, allowed_tools does not constrain it. Anthropic's example: allowed_tools=["Read"] with permission_mode="bypassPermissions" "still approves every tool, including Bash, Write, and Edit."
With Claude Code v2.1.302 or later, an SDK app starts in default mode unless configured otherwise, and set_permission_mode() (TypeScript: setPermissionMode()) changes the mode mid-session. Subagents inherit plan, acceptEdits, auto or bypassPermissions from the main session, and Anthropic warns that inheriting bypassPermissions "grants them full, autonomous system access." Under a default or dontAsk parent, Claude Code never applies a subagent's own bypassPermissions value (from v2.1.267).
Which permission mode should you use?
This table combines Anthropic's documented behavior for each mode with the guardrail that should sit next to it. The "use when" column paraphrases Anthropic's guidance; the guardrail column is our recommendation built from the same docs.
| Mode | Use it when | Matching guardrail |
|---|---|---|
dontAsk | A headless agent with a fixed, explicit tool surface (Anthropic pairs it with allowedTools for a "locked-down agent") | A short allow list with scoped rules such as Edit(/out/**), bare-name denies for tools you never need, and a PreToolUse hook for path policy |
default | An interactive app where a person approves calls through canUseTool | Keep the allow list narrow, because auto-approved calls never reach your callback; log every decision |
plan | Code review or any task where you want a proposal before changes | Review the plan, then switch modes deliberately with set_permission_mode() |
acceptEdits | Prototyping or work in an isolated directory where you trust edits | Run in a disposable checkout or container; keep Bash scoped; turn on the sandbox for shell commands |
auto | Autonomous agents that still want a check on each shell command or network request | Treat the classifier as one layer; keep deny rules and hooks for anything that must never happen |
bypassPermissions | Anthropic reserves it for CI, containers or other isolated environments | Run only inside a container or VM with no credentials, no direct network and a read-only code mount; block tools with disallowed_tools and hooks, not allowed_tools |
The canUseTool callback
The canUseTool callback is the last step. It fires for tool approvals and AskUserQuestion, receives the tool name and input, and returns allow (optionally with modified input) or deny with a message Claude sees. Anthropic stresses that "The callback never fires for auto-approved tools", and the TypeScript SDK emits a CLAUDE_SDK_CAN_USE_TOOL_SHADOWED warning when a configuration would shadow it. In Python, Anthropic's example uses streaming input plus a no-op PreToolUse hook, labeled a "required workaround" to keep the stream open.
If a check must run on every tool call, Anthropic's advice is unambiguous: use a PreToolUse hook, because hooks run before every other step and a hook deny applies even in bypassPermissions mode.
What are hooks and how do they enforce policy?
Hooks are callback functions that run your code in response to agent events. Anthropic lists their uses as blocking dangerous operations, logging and auditing tool calls, transforming inputs and outputs, requiring human approval for sensitive actions, and tracking session lifecycle.
How hooks fire
The SDK hooks page describes five steps. An event fires (for example PreToolUse before a tool runs or PostToolUse after it returns). The SDK collects hooks registered for that event, both callbacks in options.hooks and shell command hooks from settings files when the corresponding setting source is enabled. Matchers filter which hooks run, so a matcher of Write|Edit fires only for those tools and a hook with no matcher runs for every event of that type. Each callback receives the event input, and returns a decision.
Every hook input carries session_id, cwd and hook_event_name; tool hooks add tool_name and tool_input. The second argument, a tool use ID, ties a PreToolUse event to its PostToolUse event for audit trails.
The events
Python and TypeScript support different sets. These are the events most relevant to security, with support as Anthropic lists it:
| Event | Python | TypeScript | Security use |
|---|---|---|---|
PreToolUse | Yes | Yes | Block, modify or defer a tool call before it runs |
PostToolUse | Yes | Yes | Log results, append context, or replace tool output before Claude sees it |
UserPromptSubmit | Yes | Yes | Inspect or add context to prompts |
PermissionRequest | Yes | Yes | Custom permission handling |
SubagentStart and SubagentStop | Yes | Yes | Track delegated work |
PreCompact | Yes | Yes | Archive the full transcript before summarizing |
Stop | Yes | Yes | Save state at the end of a run |
PermissionDenied | No | Yes | Log auto mode denials |
SessionStart and SessionEnd | No | Yes | Start telemetry, clean up resources |
ConfigChange and InstructionsLoaded | No | Yes | Audit configuration and which CLAUDE.md or rules files load |
Anthropic's full table lists more TypeScript-only events.
What a hook can return
A callback returns top-level fields (systemMessage to show the user, and continue or continue_ in Python to decide whether the agent keeps running) plus a hookSpecificOutput object for the current operation:
- For
PreToolUse:permissionDecisionofallow,deny,askordefer, apermissionDecisionReason, andupdatedInputto rewrite the call. Adeferends the turn withstop_reasonoftool_deferredso you can resume the call later. - For
PostToolUse:additionalContextto append to the tool result, orupdatedToolOutputto replace the output before Claude sees it.
When PreToolUse hooks disagree, deny beats defer, which beats ask, which beats allow. An empty object lets the call proceed. Async outputs let the agent continue immediately but cannot block, so use them only for logging. SDK callbacks share the JSON output format of Claude Code's settings-file hooks, whose reference lists five handler types: command, http, mcp_tool, prompt and agent. A command hook that exits with code 2 blocks the action on events that can block, for example a PreToolUse hook blocks the tool call, and in that case even JSON output with an allow decision cannot let it through.
The trap in Anthropic's own example
Anthropic's first hook example blocks Write and Edit calls to .env files. Right under it, the docs carry this warning: the Write|Edit matcher limits the hook to those tools, so "If Claude writes .env with a Bash command, the hook doesn't run." The page also notes that the example compares forward-slash paths, so on Windows a call to C:\project\.env gets through.
This is the general lesson for hook policy. A hook protects the tools its matcher names, in the path format it parses. If Bash is available, write policy also has to cover Bash, either by removing Bash, by sandboxing it, or by adding a Bash hook that you accept will be a heuristic. The walkthrough below removes Bash entirely for that reason.
How do MCP servers, subagents, skills and sessions plug in?
MCP servers
The Model Context Protocol site describes MCP as "an open-source standard for connecting AI applications to external systems", covering data sources, tools and workflows. In the SDK, MCP servers can run as local processes (stdio), connect over HTTP or SSE, or run in-process inside your application as an SDK server. You can pass them in the mcpServers option or in a .mcp.json file at the project root, which loads when the project setting source is enabled.
MCP tools are named mcp__<server-name>__<tool-name>, which is what permission rules match: mcp__github__* approves a whole server, mcp__db__query one tool. The init message's tools array shows what each server exposes.
Two security points follow. First, approving mcp__server__* approves whatever that server ships today and whatever it adds in its next release, so prefer naming individual tools for anything that writes. Second, Claude Code's sandboxing page lists local MCP servers among the processes that run outside the Bash sandbox. A local MCP server runs with your process's privileges, so its package is part of your trusted computing base. The MCP specification's security best practices page covers protocol-level risks such as the confused deputy problem for proxy servers and forbids "token passthrough", where a server accepts tokens not issued for it.
Subagents
Subagents are separate agent instances spawned through the Agent tool, defined with the agents option (Anthropic's recommendation for SDK apps), as files in .claude/agents/, or used via the built-in general-purpose subagent that needs no definition. Benefits Anthropic lists are context isolation (only the final message returns), parallelism, specialized instructions and tool restrictions. The tools field is the key control: omit it and the subagent inherits every tool available to subagents. Because the general-purpose subagent exists anyway, a locked-down agent that does not need delegation should disallow Agent.
Skills
Agent Skills are SKILL.md files that Claude invokes when relevant. In the SDK they exist only on disk, with no programmatic registration API, and are discovered through setting_sources from ~/.claude/skills/, the project's .claude/skills/ and parent directories up to the repository root. The skills option limits which ones Claude may invoke. On a server, that discovery path is the risk: a default query() picks up whatever sits in the host user's home directory. Pass an empty setting_sources and an explicit skills list.
Sessions
A session is the accumulated conversation (prompt, tool calls, tool results, responses), written to disk automatically. You can continue the latest session, resume one by ID, or fork a copy. Sessions persist the conversation, not the filesystem. Transcripts live under ~/.claude/projects/ by default and do not survive a container restart without a SessionStore adapter. Because they hold every tool output the agent saw, treat them as sensitive; the TypeScript SDK's persistSession: false writes nothing to disk.
What does Anthropic document about sandboxing?
The design: filesystem and network isolation together
Anthropic's engineering post "Making Claude Code safer with sandboxing" (20 October 2025) starts from a usability problem. Claude Code's permission model is read-only by default and asks before modifications or commands, and constant approval leads to "approval fatigue", where users stop paying close attention. Sandboxing replaces many prompts with predefined boundaries. In Anthropic's internal usage, it says sandboxing "safely reduces permission prompts by 84%."
The design has two boundaries, and Anthropic insists on both. Filesystem isolation keeps a prompt-injected Claude from modifying sensitive system files. Network isolation keeps it from leaking information or downloading malware. In Anthropic's words, "effective sandboxing requires both filesystem and network isolation": without network isolation a compromised agent could exfiltrate SSH keys, and without filesystem isolation it could escape the sandbox and gain network access.
The runtime uses Linux bubblewrap and macOS Seatbelt, covers any subprocesses a command spawns, and routes network access through a Unix socket to a proxy outside the sandbox that enforces allowed domains. Anthropic open sourced it as @anthropic-ai/sandbox-runtime for other teams' agents. For Claude Code on the web, the post says git credentials never enter the sandbox; a proxy checks a scoped credential and the push target before attaching the real token.
What Claude Code's sandbox actually covers
Claude Code's sandboxing page is precise about scope, and the defaults surprise people:
| Access | Default while sandboxed |
|---|---|
| Writes | The working directory, a per-user temp directory and added directories; protected paths stay write-denied |
| Reads | Most of the machine, including credential files such as ~/.ssh and ~/.aws/credentials, unless you add filesystem.denyRead or credential settings |
| Network | No direct route out; a local proxy checks each host against allowed domains, which start empty |
| Environment variables | Inherited from Claude Code, including any secrets in its environment |
The sandbox is off by default and runs on macOS, Linux and WSL2, not native Windows. It "covers shell commands only": Read, Edit, Write, WebFetch and WebSearch follow permission rules instead, so denyRead does not stop the Read tool. Hooks, local MCP servers, excluded commands and allowed unsandboxed retries also run outside it. To put everything behind one boundary, Anthropic says to run the Claude Code process itself in a container, a VM or the sandbox runtime.
Sandboxing in the SDK
The SDK exposes the same sandbox through a sandbox setting. In Python, SandboxSettings has these fields and defaults:
| Field | Default | Meaning |
|---|---|---|
enabled | False | Turn on sandboxing for command execution |
autoAllowBashIfSandboxed | True | Auto-approve Bash commands when the sandbox is on |
excludedCommands | empty | Commands that always run unsandboxed |
allowUnsandboxedCommands | True | Let the model request an unsandboxed run with dangerouslyDisableSandbox, which falls back to the permission system |
network | none | Network configuration |
Two of those defaults matter in production. allowUnsandboxedCommands defaults to true, so set it to false when you want the boundary to hold. And the Python and TypeScript SDKs differ on failure: in Python, if enabled is true but the sandbox cannot start (on Linux it depends on bubblewrap and socat), commands run unsandboxed with a warning on stderr, while in TypeScript failIfUnavailable defaults to true. In Python, set failIfUnavailable to true explicitly so a missing dependency stops the run instead of silently removing the boundary.
How should an SDK agent run in production?
Anthropic's "Securely deploying AI agents" guide is the primary reference here. It frames the threat model plainly: agents "can take unintended actions due to prompt injection (instructions embedded in content they process) or model error", and while Claude models are designed to resist this, "defense in depth is still good practice."
Isolation choices
The guide compares four technologies:
| Technology | Isolation strength | Performance overhead | Complexity |
|---|---|---|---|
| Sandbox runtime | Good (secure defaults) | Very low | Low |
| Containers (Docker) | Setup dependent | Low | Medium |
| gVisor | Excellent (with correct setup) | Medium to high | Medium |
| VMs (Firecracker, QEMU) | Excellent (with correct setup) | High | Medium to high |
The guide is candid about limits. The sandbox runtime and containers share the host kernel, and the runtime's proxy allow-lists by client-supplied hostname without inspecting TLS, so domain fronting could reach other hosts. gVisor intercepts syscalls in user space, at a cost of up to 10 to 200x on heavy file open and close patterns. VMs add their own kernel but, Anthropic notes, are only as strong as the hypervisor.
A hardened container
The guide gives a reference docker run that drops all Linux capabilities, sets no-new-privileges, applies a seccomp profile, makes the root filesystem read-only with small tmpfs mounts for /tmp and the home directory, removes all network interfaces, caps memory, CPU and process count, runs as a non-root user, and mounts code read-only. With no network interface, the agent's only way out is a mounted Unix socket connected to a proxy on the host, which can enforce allowed domains, inject credentials and log traffic. Anthropic says this is the same architecture sandbox-runtime uses. It also warns: "Avoid mounting sensitive host directories like ~/.ssh, ~/.aws, or ~/.config." Optional extras are user namespace remapping and a private IPC namespace.
Least privilege and secrets
For credentials, Anthropic's recommended approach is a proxy outside the agent's boundary that injects credentials into outgoing requests, so "the agent never sees the actual credentials", the proxy can enforce an endpoint allow list and log every request, and secrets live in one place. Claude Code and the SDK support two routes. ANTHROPIC_BASE_URL sends sampling requests to your proxy as plaintext HTTP, so the proxy can add the API key. HTTP_PROXY and HTTPS_PROXY route all HTTP traffic, but HTTPS through a regular proxy is an opaque CONNECT tunnel, so injecting credentials into other services needs either a custom tool or MCP server that makes the authenticated call outside the boundary, or a TLS-terminating proxy whose CA the agent trusts. The guide also notes that not every program honors the proxy variables; Node.js fetch() ignores them by default.
Even a read-only mount can leak secrets. Anthropic lists files to exclude or sanitize before mounting a code directory, including .env files, ~/.git-credentials, cloud CLI credential files, ~/.docker/config.json, ~/.kube/config, .npmrc, .pypirc, service account JSON files and private keys.
For environment variables, Claude Code documents CLAUDE_CODE_SUBPROCESS_ENV_SCRUB. Set to 1, it strips credentials from the environments of subprocesses Claude Code starts, such as Bash commands, hooks and stdio MCP servers, recognizing them by variable name or value, while leaving GitHub tokens and proxy settings in place.
Cloud deployments
In the cloud, the guide suggests a private subnet with no internet gateway, egress blocked except to your proxy, minimal IAM for the agent's service account and full proxy logging. The hosting guide adds that one session is one subprocess, so give each session its own cwd when they need separate filesystems.
What are the risks?
Prompt injection through tool output
Anthropic names this first: agents' behavior "can be influenced by the content they process: files, webpages, or user input", such as a README with unusual instructions. Every tool result is content Claude reads, from web pages and repository files to MCP responses and subagent reports. Anthropic's mitigations are layered. WebSearch results are summarized rather than passed raw into context. From v2.1.210, Claude Code scans a subagent's final message for instruction-shaped patterns before the parent reads it, neutralizing imitations of harness control tags and escaping lines that start with Human: or Assistant: so they cannot imitate a turn boundary. And the sandbox and network controls exist so that "even a successful prompt injection is fully isolated."
What you control: what the agent reads (narrow cwd, no web tools unless needed), what it can do after reading (deny rules and hooks), and where data can go (no direct network, proxy allow list). A PostToolUse hook can also inspect or replace tool output with updatedToolOutput before Claude sees it, which is a place to strip content you never want in context.
Over-broad tool permissions
The most common misconfigurations come straight from the documented behavior:
- Using
allowed_toolsas if it were a restriction. It only pre-approves; unlisted tools still exist, and inbypassPermissionseverything is approved. - Bare-name allows such as
BashorWrite, which approve every call to that tool and skip yourcanUseToolcallback. - Wildcard MCP approvals such as
mcp__server__*on servers that can write or send. - Leaving the
Agenttool available in a locked-down agent, so the built-in general-purpose subagent can run with inherited tools. - Relying on
Bash(pattern)deny rules to contain shell behavior, when they match commands as written.
Credential exposure
By default a sandboxed Bash command can read ~/.ssh and ~/.aws/credentials and inherits environment secrets such as ANTHROPIC_API_KEY, unless you scrub them or keep them outside with the proxy pattern. Transcripts can capture secrets tools printed, and a code mount can carry forgotten .env or .npmrc files.
Dependency hygiene in the agent's own package tree
An agent is software, and it ships a dependency tree like any other service. With the Agent SDK that tree has several layers:
- The SDK package itself, from npm or PyPI, plus its transitive dependencies.
- The bundled Claude Code binary. Both SDKs bundle a native binary, and the changelogs tie SDK releases to Claude Code versions; the TypeScript changelog's latest entry at the time of writing reads "Updated to parity with Claude Code v2.1.310." Upgrading the SDK upgrades the agent runtime, so treat SDK bumps like runtime upgrades, with tests. The quickstart also notes that if pip installs the source distribution instead of a platform wheel, no binary is bundled.
- MCP server packages. Local stdio servers are often installed from a package registry at launch and run outside the Bash sandbox with your process's privileges.
- Skills, plugins and settings loaded from the filesystem through
setting_sources. - Everything the agent installs while working. If it can run
npm installorpip install, each run can pull a different tree.
The practical controls are ordinary supply chain hygiene: pin exact versions, commit lockfiles, install with hash checking (pip's hash-checking mode with --require-hashes, and --only-binary :all: to refuse source distributions), build the agent into an image instead of installing at runtime, keep setting_sources empty in production, and keep an inventory of what is in the image so you can answer "which agents carry this version?" when an advisory lands.
Step by step: a read-only agent with a write fence, in a container
This walkthrough builds a Python agent that reviews a repository and writes its findings only into one output directory. It has no shell, no web access and no subagents, and it runs in a hardened container whose only route out is an allow-listed proxy. The Python and TypeScript option names come from Anthropic's SDK references; the Docker flags come from Anthropic's secure deployment guide and Docker's documentation.
Step 1: install the SDK and set credentials
python3 -m venv .venv && . .venv/bin/activate
pip install claude-agent-sdk
python -c "import claude_agent_sdk, importlib.metadata as m; print(m.version('claude-agent-sdk'))"
export ANTHROPIC_API_KEY="your-api-key"
For TypeScript, the equivalent is npm install @anthropic-ai/claude-agent-sdk. Anthropic's quickstart notes that the SDK does not load .env files automatically, so the key must be in the environment where the agent runs. Record the version you tested; you will pin it in Step 5.
Step 2: a read-only agent
Start with the smallest surface that can do the job. This agent can read and search, and nothing else.
# agent_readonly.py
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
options = ClaudeAgentOptions(
cwd="/workspace",
setting_sources=[], # ignore ~/.claude and project settings
allowed_tools=["Read", "Glob", "Grep"], # auto-approved
disallowed_tools=["Bash", "Write", "Edit", "NotebookEdit",
"WebFetch", "WebSearch", "Agent"], # removed from context
permission_mode="dontAsk", # anything else is denied, never prompted
max_turns=30,
max_budget_usd=2.0,
)
async def main():
async for message in query(
prompt="List the dependency manifests in this repository and summarize what each declares.",
options=options,
):
if isinstance(message, ResultMessage):
print(message.subtype)
print(getattr(message, "result", ""))
asyncio.run(main())
setting_sources=[] stops the agent loading skills, hooks, MCP servers and rules from the host. Bare-name disallowed_tools entries remove tools from Claude's context. dontAsk turns every would-be prompt into a denial. The caps stop an open-ended prompt from running indefinitely.
Step 3: let it write, but only into one directory
Now allow writes, scoped two ways. First, a declarative rule: Edit(/out/**) with a single leading slash anchors at the working directory, so it covers /workspace/out/**, and Edit rules govern Write and NotebookEdit too. Second, a PreToolUse hook that resolves the real path (following symlinks) and denies anything outside the output directory. The hook runs first in the evaluation order and its deny holds in every mode, so it still protects you if someone later changes the mode or adds a broad allow rule.
# agent.py
import asyncio
import json
import os
import sys
from pathlib import Path
from claude_agent_sdk import (
ClaudeAgentOptions,
ClaudeSDKClient,
HookMatcher,
ResultMessage,
)
WORKSPACE = Path(os.environ.get("AGENT_WORKSPACE", "/workspace")).resolve()
OUTPUT_DIR = (WORKSPACE / "out").resolve()
# Built-in tools that write files, and the input field holding the path.
WRITE_TOOLS = {"Write": "file_path", "Edit": "file_path", "NotebookEdit": "notebook_path"}
def inside(target: str, base_cwd: str, root: Path) -> bool:
path = Path(target)
if not path.is_absolute():
path = Path(base_cwd) / path
resolved = path.resolve() # follows symlinks in existing path components
return resolved == root or root in resolved.parents
async def fence_writes(input_data, tool_use_id, context):
field = WRITE_TOOLS.get(input_data.get("tool_name", ""))
if field is None:
return {} # not a write tool; let the normal checks decide
target = input_data.get("tool_input", {}).get(field, "")
if target and inside(target, input_data.get("cwd", str(WORKSPACE)), OUTPUT_DIR):
return {} # pass on; deny and allow rules still apply
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": f"Writes are only allowed under {OUTPUT_DIR}",
}
}
async def audit(input_data, tool_use_id, context):
record = {
"event": input_data["hook_event_name"],
"tool": input_data.get("tool_name"),
"tool_use_id": tool_use_id,
"session": input_data.get("session_id"),
}
print(json.dumps(record), file=sys.stderr)
return {}
options = ClaudeAgentOptions(
cwd=str(WORKSPACE),
setting_sources=[],
allowed_tools=["Read", "Glob", "Grep", "Edit(/out/**)"],
disallowed_tools=["Bash", "WebFetch", "WebSearch", "Agent"],
permission_mode="dontAsk",
max_turns=40,
max_budget_usd=3.0,
hooks={
"PreToolUse": [HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[fence_writes])],
"PostToolUse": [HookMatcher(matcher=None, hooks=[audit])],
},
)
async def main():
prompt = os.environ.get(
"TASK_PROMPT",
"Review the dependency manifests in this repository and write a findings "
"report to out/report.md. Do not modify any other file.",
)
async with ClaudeSDKClient(options=options) as client:
await client.query(prompt)
async for message in client.receive_response():
if isinstance(message, ResultMessage):
print(message.subtype)
asyncio.run(main())
Test the fence before you trust it. Ask the agent to "update README.md with a summary" and confirm the run log shows a denial with your reason, then ask for out/report.md and confirm the file appears. Also try a relative path that climbs out (out/../README.md); resolve() normalizes it and the hook denies it. Anthropic's docs note the Write and Edit matcher does not see Bash, which is why Bash is removed here rather than filtered.
In TypeScript, pass the same options in camelCase and register the callback under PreToolUse with the same matcher. One honest limit: a path hook checks at decision time and the write happens moments later, which is one more reason to run it inside a container rather than relying on the hook alone.
Step 4: build a pinned image
# requirements.txt, generated by your lock tool with hashes, for example:
# claude-agent-sdk==<the version you tested> --hash=sha256:<hash>
# plus every transitive dependency, each with its own --hash
FROM python:3.12-slim
RUN useradd --create-home --uid 1000 agent
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --require-hashes --only-binary :all: -r requirements.txt
COPY agent.py .
USER agent
ENTRYPOINT ["python", "/app/agent.py"]
Hash-checking mode makes pip refuse any file whose hash does not match the lock, and --only-binary :all: refuses source distributions. That second flag matters doubly here: Anthropic's quickstart notes that a source install of the Python SDK bundles no Claude Code binary.
Step 5: give it an allow-listed way out
Anthropic's reference container uses no network at all plus a Unix socket to a host proxy. A simpler pattern that keeps the same idea is a Docker network created with the --internal flag, which Docker documents as restricting external access to the network, plus one proxy container attached to both that internal network and the outside. The proxy only permits the Claude API host. For Squid, the dstdomain ACL type does this:
# squid.conf
http_port 3128
acl claude_api dstdomain api.anthropic.com
http_access allow claude_api
http_access deny all
docker network create --internal agent-net
docker network create egress-net
docker run -d --name egress-proxy --network egress-net \
-v "$PWD/squid.conf":/etc/squid/squid.conf:ro your-squid-image
docker network connect agent-net egress-proxy
If you add tools later that need other hosts, add each host to the allow list on purpose rather than opening the network.
Step 6: run the agent in the hardened container
Check the two host paths first. The code directory is mounted read-only; the output directory must exist and be owned by UID 1000.
ls -ld "$PWD/repo" "$PWD/out"
sudo chown 1000:1000 "$PWD/out"
docker run --rm \
--network agent-net \
--cap-drop ALL \
--security-opt no-new-privileges \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /home/agent:rw,noexec,nosuid,size=500m \
--memory 2g --cpus 2 --pids-limit 100 \
--user 1000:1000 \
-v "$PWD/repo":/workspace:ro \
-v "$PWD/out":/workspace/out:rw \
-e HTTPS_PROXY=http://egress-proxy:3128 \
-e CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 \
--env-file ./agent.env \
agent-image
agent.env holds ANTHROPIC_API_KEY. With Bash removed the agent has no shell to print it, and the scrub covers future subprocess tools; the stronger pattern is to keep the key outside entirely with ANTHROPIC_BASE_URL pointing at a credential-injecting proxy. Strip .env, .npmrc, .pypirc, keys and cloud credential files from the copy you mount. The /home/agent tmpfs holds session transcripts, which vanish with the container. If the process needs another writable path, add a narrow tmpfs rather than dropping the read-only root, and add gVisor's runsc runtime for kernel isolation.
Step 7: watch the first runs
Read the audit lines on stderr for each tool call, check the ResultMessage subtype (success, error_max_turns or error_max_budget_usd), and compare the output directory with what you expected. Only then widen anything.
Common mistakes
- Treating
allowed_toolsas an allow list that blocks everything else. It pre-approves. Unlisted tools remain available, andbypassPermissionsapproves them all. - Putting must-never-happen checks in
canUseTool. Auto-approved calls never reach it. Use aPreToolUsehook. - Writing a Write and Edit hook and leaving Bash on. Anthropic's own example warns that Bash bypasses that matcher.
- Assuming the sandbox covers the whole agent. It covers shell commands. Read, Write, WebFetch, hooks and local MCP servers run outside it.
- Assuming the sandbox hides credentials. By default it allows reads of most of the machine, including
~/.ssh, and inherits environment secrets. - Leaving
allowUnsandboxedCommandsat its default of true, or running Python withoutfailIfUnavailable, so a missingbubblewrapsilently disables the boundary. - Shipping with default
setting_sources. A production agent then loads skills, hooks, MCP servers and rules from whatever host it lands on. - Mixing up path anchors.
Edit(/secrets/**)indisallowed_toolsis relative to the working directory;Edit(//secrets/**)is the absolute path. - Approving
mcp__server__*on a server that can write, and inheriting every new tool it adds. - Putting rules only in the opening prompt. Compaction can summarize them away.
- No turn or budget cap, and no headroom for the response that crosses the cap.
- Installing the SDK or MCP servers at container start. Each start can resolve a different dependency tree. Build pinned images instead.
FAQ
Is the Claude Agent SDK the same as the Claude Code SDK?
Yes, renamed. Anthropic announced the rename on 29 September 2025, and the Python changelog marks it at version 0.1.0. The packages moved from @anthropic-ai/claude-code and claude-code-sdk to @anthropic-ai/claude-agent-sdk and claude-agent-sdk, and the Python options type became ClaudeAgentOptions.
What languages does the Claude Agent SDK support?
Python 3.10 or later and TypeScript on Node.js 18 or later. For other languages, Anthropic documents running the Claude Code CLI as a subprocess with -p and JSON output.
What is the difference between allowed_tools and disallowed_tools?
allowed_tools auto-approves the tools or patterns you list and leaves every other tool available. disallowed_tools blocks: a bare name removes the tool from Claude's context, and a scoped pattern denies matching calls in every mode, including bypassPermissions.
Which permission mode should a headless production agent use?
Anthropic pairs dontAsk with a short allowedTools list for a locked-down agent, because anything that would prompt is denied. Reserve bypassPermissions for isolated environments such as containers, and remember that allowed_tools does not limit it.
Can a hook block a tool call in bypassPermissions mode?
Yes. Hooks run before every other permission step, and Anthropic states that a hook deny applies even in bypassPermissions mode. A hook allow, by contrast, does not override deny or ask rules.
Does the Claude Code sandbox protect my SSH keys?
Not by default. Claude Code's sandbox allows sandboxed commands to read most of the machine, including ~/.ssh and ~/.aws/credentials, unless you add read denials or credential settings, and it does not cover the Read tool. Its main protection against theft is network isolation through the proxy. For a production agent, do not mount credentials at all.
Does the sandbox apply to MCP servers?
No. Claude Code's sandboxing page lists local MCP servers among the processes that run outside the Bash sandbox. To put them behind a boundary, run the whole agent process in a container, VM or the sandbox runtime.
How do I stop an SDK agent from loading my local skills and settings?
Pass an empty list to setting_sources (Python) or settingSources (TypeScript), and set the skills option explicitly. Upgrade past Python SDK 0.1.59 first, since earlier versions treated an empty list as "use the defaults."
How do I keep the API key away from the agent?
Anthropic's recommended pattern is a proxy outside the agent's boundary. Set ANTHROPIC_BASE_URL to that proxy, which adds the key to each request, so the agent process never holds it. At minimum, set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 so subprocesses do not inherit credentials.
Where Safeguard fits
Safeguard does not run or sandbox your agent, and it does not replace any control in this post. Its fit is the agent's software supply chain. The SDK package, its transitive dependencies, the MCP server packages you launch and the base image you build on are all third party components with versions that receive advisories. The Safeguard CLI can scan the agent's repository and its built image (safeguard scan --dir . and safeguard scan --image agent-image), match components against vulnerability databases, use reachability analysis to rank findings, and evaluate policies and gates in the platform, so a build that pulls a vulnerable version can fail before it ships. The platform shows which projects contain a given component and version, which is how you answer "which of our agents carry this SDK release?" Governing an agent's live tool traffic is not part of what we describe here as generally available: MCP Guardrail, which generates guardrail proxy URLs for MCP servers, is in early access.
If you want Claude itself to use Safeguard, add Safeguard from Claude's connector directory on web, desktop or mobile and sign in with OAuth. The first demo below shows choosing which Safeguard tools Claude may use, the same least privilege idea as allowed_tools.
An SDK agent is a service built from packages: its SDK, bundled runtime, MCP servers, open source dependencies and base image all carry versions that will one day have advisories. The fit here is the agent's own dependency tree, not its prompts or tool calls, so the steps below apply to the packages and images you build and ship around the agent.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
None of these demos shows the Claude Agent SDK. They show Safeguard's own agent surfaces handling the same questions this post is about: which tools an agent may use, and who approves each action. You can download Safeguard and follow the CLI docs to try them.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. In practice, the simpler route is to add Safeguard from Claude's connector directory and sign in; either way, picking individual tools is the same discipline as naming single MCP tools in allowedTools instead of a server-wide wildcard.
Click through it at your own pace, or open it full screen.
This is a real Safeguard CLI session. Asked to fix a lodash CVE, the assistant reads the finding and the advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing. It then shows /permissions, the same idea as the SDK's permission modes: decide up front how much the agent may do without asking.
Click through it at your own pace, or open it full screen.
This Android session sends work to a computer running the CLI and approves the command on the phone with Allow once, Allow for turn or Deny. It is a human-in-the-loop approval step, the role canUseTool plays in an SDK app, moved onto a device you carry.
Click through it at your own pace, or open it full screen.
Quick check
An SDK agent runs with allowed_tools set to Read and permission_mode set to bypassPermissions. Can it run Bash?
Yes. Anthropic's permissions page states that allowed_tools does not constrain bypassPermissions: unlisted tools fall through to the mode, which approves them. To block Bash, remove it with disallowed_tools or deny it in a PreToolUse hook.
You turned on the Claude Code sandbox. Does it stop the Read tool from opening ~/.ssh files?
No. The sandbox wraps shell commands only. Built-in file tools follow permission rules, and by default sandboxed commands can also read most of the machine, including ~/.ssh. Keep credentials out of the agent's environment and mounts entirely.
Sources
- Anthropic: Agent SDK overview
- Anthropic: How the agent loop works
- Anthropic: Configure permissions (Agent SDK)
- Anthropic: Handle approvals and user input (Agent SDK)
- Anthropic: Intercept and control agent behavior with hooks (Agent SDK)
- Anthropic: Claude Code hooks reference
- Anthropic: MCP in the Agent SDK
- Anthropic: Subagents in the Agent SDK
- Anthropic: Agent Skills in the SDK
- Anthropic: Work with sessions (Agent SDK)
- Anthropic: Hosting the Agent SDK
- Anthropic: Securely deploying AI agents
- Anthropic: Agent SDK quickstart
- Anthropic: Migrate to Claude Agent SDK
- Anthropic: Agent SDK reference, Python
- Anthropic: Claude Code sandboxing
- Anthropic: Claude Code environment variables
- Anthropic: Building agents with the Claude Agent SDK (29 September 2025)
- Anthropic Engineering: Making Claude Code safer with sandboxing (20 October 2025)
- GitHub: anthropics/claude-agent-sdk-python README
- GitHub: claude-agent-sdk-python CHANGELOG
- GitHub: anthropics/claude-agent-sdk-typescript README
- GitHub: claude-agent-sdk-typescript CHANGELOG
- Model Context Protocol: What is MCP?
- Model Context Protocol: Security best practices
- pip documentation: Secure installs
- Docker documentation: docker network create
- Squid configuration reference: acl
Last reviewed: 24 October 2026. The Agent SDK and Claude Code release often and option names, defaults and version requirements change; check the linked Anthropic documentation before you build.