In part one of this series, Nicolas explained why AI agents need cloud sandboxes: they execute real commands, process untrusted content, and have access to your secrets. Cloud environments like Upsun give you full-stack isolation with production data that local sandboxing can’t match. This article is the hands-on follow-up. It shows how to build an in-container sandbox on Upsun using standard Linux tools, and then how Claude Code and Codex handle the same problem with their built-in sandboxing.Documentation Index
Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
Use this file to discover all available pages before exploring further.
What needs protecting
Simon Willison’s “lethal trifecta” describes what makes agents risky: they have access to sensitive data, they process untrusted input, and they can communicate externally. A sandbox limits the damage by restricting what the agent’s shell can reach. The primary concerns are:- Secrets: environment variables (API keys, tokens, database URLs) and credential files.
- Network: outbound connections that could exfiltrate data.
- Filesystem: sensitive paths (
.ssh,.aws, application config).
It’s the harness, not the AI
An LLM doesn’t execute commands. It outputs text like “please runcat /etc/passwd.” Deterministic code (the agent harness) decides whether to actually run it. Claude Code, Codex, and similar tools all have a layer of code between the model and the shell.
Sandboxing wraps that deterministic code. You don’t need to understand AI to understand sandboxing. The question is: when the harness runs a shell command, what can that shell access?
This also means sandboxing scope varies by tool type. Shell commands go through the sandbox. MCP tools are separate processes with their own permission model. The sandbox configuration depends on which tools the harness exposes.
Upsun’s container security
Upsun containers already provide a foundation:- Rootless containers: each application runs in its own unprivileged container with minimal Linux capabilities and no root access.
- Namespace isolation: containers are separated by Linux namespaces, so environments and services can’t see each other’s processes or network.
- Syscall filtering: a seccomp profile restricts the system calls available inside each container.
- Incoming firewall: only ports 22 (SSH), 80 (HTTP), and 443 (HTTPS) are open. Inter-container connections require an explicit relationship defined in configuration.
- Read-only application filesystem:
/appis mounted as a read-only squashfs. Code can’t be modified at runtime.
Building a sandbox from Linux primitives
Standard Linux tools handle all three concerns with no additional packages.env -i strips environment variables. unshare --user --net creates user and network namespaces, blocking /proc access and all network traffic. Bubblewrap adds mount namespace control (read-only filesystem, writable scratch space) and seccomp filter passthrough for additional hardening.
This section shows the building blocks step by step. If you’re using Claude Code or Codex, these are the same primitives their sandboxes use under the hood.
Step 1: Strip environment variables
The simplest sandboxing measure. Run the agent’s command withenv -i, passing only the variables it needs:
env inside the sandboxed process and you’ll see only three variables. But there’s a critical gap.
The /proc bypass
On Upsun, all processes within a container run as the same user (web). Even after stripping the environment, the sandboxed process can read other processes’ environment variables and memory through /proc:
ANTHROPIC_API_KEY, GITHUB_TOKEN, and database credentials from every other process in the container. Process memory is also readable cross-process — parsing /proc/<pid>/maps and seeking into /proc/<pid>/mem lets a same-UID process extract secrets that were never stored in environment variables. The env -i stripping is completely bypassed.
Step 2: Add user and network namespace isolation
Theunshare command creates new namespaces. --user remaps the UID to 65534/nobody, preventing reads of files owned by the outer web user. --net creates an empty network namespace with no interfaces, blocking all network access:
/proc bypass returns nothing. On Upsun, user namespace isolation combines with hidepid=invisible on /proc: other processes’ entries are entirely invisible, not just permission-denied. And the empty network namespace means TCP, UDP, and DNS all fail.
At this point, env -i strips secrets, --user blocks /proc, and --net blocks network. The remaining gap is filesystem control.
Step 3: Add filesystem controls with bubblewrap
unshare can’t provide mount isolation in Upsun’s containers. Bubblewrap fills this gap:
- Mount namespace:
--ro-bind / /makes the root filesystem read-only,--tmpfs /tmpprovides writable scratch space. - Seccomp passthrough:
--seccomp FDapplies a compiled BPF filter for additional hardening (blockingptrace,io_uring). - Bubblewrap handles
--unshare-userand--unshare-netinternally, so it replaces theunsharecommand from Step 2.
bubblewrap package. On a composable image, add it to your stack:
ptrace and io_uring even if namespace isolation were bypassed:
--seccomp 10 flag tells bubblewrap to read a compiled BPF filter from file descriptor 10. The 10< net-deny.bpf shell redirect opens the file on that descriptor. The --unshare-net flag provides network namespace isolation (same as Step 2), while the seccomp filter adds a second layer blocking network syscalls, ptrace, and io_uring.
This requires the libseccomp Python bindings. On a composable image, add libseccomp and python313Packages.seccomp to your Nix packages.
Putting it together
The complete sandbox layers four concerns:| Layer | Tool | What it does | Why it’s needed |
|---|---|---|---|
| Env stripping | env -i | Strips environment variables | Removes secrets from the process |
| Process isolation | unshare --user | UID remapping to 65534/nobody | Prevents /proc bypass |
| Network | unshare --net | Empty network namespace | Prevents data exfiltration |
| Filesystem | bubblewrap | Read-only root, tmpfs scratch | Prevents filesystem modification |
unshare --user --net + env -i is a viable sandbox covering all three concerns.
Agent tools that already do this
Claude Code and Codex both sandbox shell commands using the same Linux primitives described above. Both use bubblewrap for filesystem and namespace isolation. Codex auto-detects Upsun’s container constraints; for Claude Code you opt into a container mode in srt’s config (shown below). The other differences are in how they handle network filtering and environment variables.Claude Code with sandbox-runtime
sandbox-runtime (@anthropic-ai/sandbox-runtime) is Anthropic’s sandboxing layer for Claude Code. Install it alongside Claude Code:
enableWeakerNestedSandbox setting in the config below). It provides:
- Filesystem deny rules: sensitive paths (
.ssh,.aws, credential files) are hidden using mount overlays. - Network proxy: an HTTPS proxy filters outbound connections by domain allowlist. You configure which domains the sandboxed process can reach (e.g. package registries), and everything else is blocked. The proxy works for HTTP-aware tools (curl, wget, pip, npm) that respect
HTTP_PROXY. Programs that ignore the proxy variable get no network access at all, not unfiltered access. - Namespace isolation: user, PID, and network namespaces via bubblewrap. The default mode mounts a fresh
/proc, which fails in unprivileged containers. SettingenableWeakerNestedSandbox: trueswitches to a bind-mounted host/procand keeps the user and PID namespaces, so cross-process/proc/<pid>/{environ,maps,mem}reads still return ENOENT — only/proc/<pid>/cmdlineof host processes remains visible.
~/.srt-settings.json:
env -i as described above.
Codex
Codex ships a static binary with bubblewrap and seccomp compiled in. Install it:--full-auto mode, Codex blocks all outbound network by default using a network namespace plus seccomp syscall filtering. The sandboxed process can’t make TCP connections, UDP sends, or DNS queries. This is stronger than a proxy — it blocks raw TCP, not just HTTP — but it’s all-or-nothing: you can’t allow specific domains.
Important: Codex doesn’t strip environment variables by default. Fix this with configuration:
inherit="core" setting passes only PATH, HOME, SHELL, and USER. The ignore_default_excludes=false setting enables default filters that strip variables matching *KEY*, *SECRET*, and *TOKEN* (catching ANTHROPIC_API_KEY, GITHUB_TOKEN, etc.). The exclude setting adds a pattern to also strip PLATFORM_* variables.
Codex also needs a writable home directory. On Upsun, set HOME and CODEX_HOME to a writable path:
What to check
After setting up sandboxing, verify it works. SSH into your Upsun environment and run these checks inside the sandbox:env shows no secrets, /proc returns nothing, the network connection is blocked, and /app is read-only, your sandbox is working.
Comparing approaches
| Claude Code + srt | Codex (hardened) | unshare + env | unshare + bwrap | |
|---|---|---|---|---|
| Env stripping | not stripped | config required | env -i | env -i |
| Network | domain proxy (net namespace) | all blocked (net namespace + seccomp) | all blocked (net namespace) | all blocked (net namespace + seccomp) |
| Filesystem | deny rules (mount overlays) | ro-bind + writable roots | no isolation | ro-bind + tmpfs |
/proc isolation | prevented (user + PID ns) | prevented | prevented | prevented |
| Setup effort | low | medium | low | medium |
env -i. Codex provides env stripping through configuration but blocks all network traffic rather than filtering by domain.
The minimal sandbox (unshare --user --net + env -i) covers all three concerns — env stripping, /proc isolation, and network blocking — with zero dependencies. Add bubblewrap when you need filesystem controls.
For most users running Claude Code or Codex on Upsun, using the agent’s built-in sandbox with the configuration shown above is sufficient. The primitives are useful if you’re building your own agent harness or want to understand what happens under the hood.
To get started with Upsun, create a free trial account. For more on running AI workloads on the platform, check out other articles tagged with ai-agents.