Claude Agent SDKclaude-agent-sdk 0.2.157 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

Secure deployment

Secure deployment is the set of boundaries you put around an agent that can run code, read files and call out to the network. The agent decides what to do as it goes, so the boundaries, rather than its code path, are what bound the damage.

Last updated: 20 Oct, 2026 · Claude Agent SDK

An agent's actions are shaped by the content it reads. A file, a web page or a message can carry instructions, and the agent may act on them in ways nobody intended, which is the risk known as prompt injection. Claude models are trained to resist it, and defence in depth is still the practice: if an agent is talked into sending data somewhere, a network control can refuse the request outright. The secure deployment guide is the source for this lesson.

Not every deployment needs all of this. One person running an agent on their own laptop has a different problem from a service handling other people's data. Read it as a ladder and climb as far as your situation warrants.

What Claude Code brings already

  • Permissions. Every tool and every shell command can be allowed, blocked, or put to a person, with glob patterns for rules such as allowing one command family.
  • Command parsing. A shell command is parsed into a syntax tree and matched against your rules before it runs. One that does not parse cleanly, or does not match an allow rule, needs approval, and a few constructs always do.
  • Summarised web search. Search results are summarised rather than pasted into the context, which narrows what a hostile page can say to the agent.
  • Sandbox mode. Shell commands can run with the filesystem and the network restricted.

The command parsing is a permission gate and not a sandbox. Beyond a few built-in safety checks it does not work out from a command's target whether that command is dangerous. Treat it as the thing that applies your rules, and put the isolation underneath it.

Turning the sandbox on from the options

The sandbox option is a settings dictionary the SDK forwards to Claude Code, which is the side that enforces it. On this install the forwarding is visible in the command line, so the run below shows the settings arriving even though nothing sandboxed is executed here.

Example
import shlex

from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk._internal.transport.subprocess_cli import SubprocessCLITransport

# failIfUnavailable stops the run rather than falling back to an unsandboxed shell
box = {"enabled": True, "failIfUnavailable": True,
       "network": {"allowedDomains": ["api.github.com"]}}

# the settings are forwarded whole; Claude Code is what applies them
options = ClaudeAgentOptions(sandbox=box, allowed_tools=["Bash"], cli_path="claude")
started = SubprocessCLITransport(prompt="", options=options)._build_command()
print(shlex.join(started[1:]))

Reading the settings flag

  • The whole dictionary went across as --settings. Fields the Python type does not declare, failIfUnavailable and filesystem among them, are forwarded with the rest.
  • failIfUnavailable matters on Python. The documented Python default is to carry on unsandboxed with a warning when the sandbox cannot start, which differs from the TypeScript SDK. Setting it makes the run stop instead, with an error result you can read.
  • The allowed domains bound the network. Sandboxed commands reach the names on the list and no others, and a denied list wins over the allowed one.
  • Nothing is enforced here. This run starts no process and executes no shell command. A real sandbox needs platform support, and on Linux tools such as bubblewrap.

The sandbox proxy matches on the hostname a client asks for and does not decrypt the traffic, so a determined program inside the sandbox can reach a host that is not on the list. Where that matters, terminate TLS at a proxy of your own.

Choosing how strong the isolation has to be

TechnologyIsolationOverheadSetup
Sandbox runtimeGood, with sensible defaultsVery lowLow: no container or networking work
ContainersDepends entirely on the configurationLowMedium
gVisorExcellent when set up correctlyMedium to highMedium
Virtual machinesExcellent when set up correctlyHighMedium to high

Sandbox runtime is the low-effort step up, using the operating system's own primitives for the filesystem and routing the network through a built-in proxy. Its limit is that the sandboxed processes share your kernel. gVisor answers that by handling most system calls in userspace before they reach the real kernel, at the cost of slower file-heavy work. Virtual machines give each agent its own kernel, and a microVM can boot in a fraction of a second.

Hardening the container the agent runs in

Where a container is the boundary, the flags are what make it one. This shape drops every Linux capability, forbids gaining privileges, keeps the root filesystem immutable with one small writable scratch area, removes the network outright, and mounts the code read-only.

bash
# no capabilities, nothing writable but one scratch mount, and no network at all
docker run --cap-drop ALL --security-opt no-new-privileges --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m --network none \
  --memory 2g --pids-limit 100 --user 1000:1000 \
  -v /path/to/code:/workspace:ro \
  -v /var/run/proxy.sock:/var/run/proxy.sock:ro \
  agent-image

With no network interfaces, the only way out is the Unix socket mounted at the end, which connects to a proxy running outside the container. The proxy is where domain allowlists are enforced, credentials are added and traffic is logged. An agent that has been talked into exfiltrating data cannot reach an arbitrary server, because there is no route to one.

Keeping the credential outside the agent

The strongest pattern for secrets is to not put them where the agent is. A proxy outside the boundary adds the credential to each outgoing request: the agent makes the call, the proxy makes it authenticated. The agent never holds the secret, the proxy enforces which endpoints are reachable, and every request is logged in one place.

bash
# sampling requests only, in plaintext, so the proxy can add the header itself
export ANTHROPIC_BASE_URL="http://proxy.internal:8080"

# everything the process speaks, as an opaque tunnel unless the proxy terminates TLS
export HTTP_PROXY="http://proxy.internal:8080"
export HTTPS_PROXY="http://proxy.internal:8080"

The two differ in what the proxy can see. Requests to the Claude API sent through the base URL arrive in plaintext, so a header can be added. Ordinary HTTPS to other services arrives as an encrypted tunnel, and adding anything to it means a TLS-terminating proxy with its certificate installed in the agent's trust store. An MCP server that makes the authenticated call outside the boundary avoids that work: the agent sees the tool, not the secret.

Never put a real key into a lesson file, a repository or an image layer. Read it from a secret manager at start, or route the call through the proxy so the agent's environment has no key to leak.

Mounting code read-only, and what to keep out

Mounting a directory read-only stops the agent writing to it. It does not stop the agent reading a secret that is sitting in it, and a working tree usually has several. Copy in the source the task needs, or filter the mount.

  • .env and .env.local, which hold API keys and database passwords.
  • ~/.aws/credentials, ~/.config/gcloud/ and ~/.azure/, which hold cloud access.
  • ~/.git-credentials and ~/.docker/config.json, which hold tokens in plain text.
  • ~/.kube/config, .npmrc, .pypirc, and any *.pem or *.key file.

Where the agent has to write, a memory-backed scratch mount is cleared when the container stops. Where the writes are meant to survive, mount a volume for them and keep it away from anything sensitive.

The sandbox option against a hardened container

sandbox optionHardened container
What it boundsShell commands the agent runsThe whole process, the SDK included
Where it is setIn your Python, per callIn the deployment, outside the code
Network controlDomain lists, matched on the requested hostnameNo interface at all, with a proxy on a socket
Who enforces itClaude Code, using the operating system's primitivesThe container runtime and the kernel

How far up the ladder to climb

  • One developer on one machine: the permissions system and sandbox mode are a reasonable stopping point.
  • An agent reading content from outside, such as web pages or customer attachments: add the container and the egress proxy.
  • Many tenants on shared infrastructure: add per-tenant isolation, per-tenant egress rules, and kernel-level isolation if the content is untrusted.
Watch out. Allowing a Unix socket inside the sandbox can hand back everything the sandbox took away. Allowing the Docker socket gives full control of the host through the Docker API, whatever else the configuration says. Allow a socket only when the agent cannot work without it, and know what reaching it means.
Try it yourself
  • Add deniedDomains beside the allowed list and read the settings flag again.
  • Drop failIfUnavailable and say which of the two behaviours you would want in a service.
  • Add "filesystem": {"denyWrite": ["/work/.env"]} to the dictionary and confirm it is forwarded.

You understood something today that you didn't yesterday.