Getting started#
This tutorial gets you to a working, sandboxed Claude Code — and proves the
sandbox is intact before you trust it. You’ll be working inside a
Debian/Ubuntu devcontainer running as root (the typical rootless-Podman
pattern; rootless Docker works too).
There are two ways in. Pick whichever fits how you already work.
The quick way: use claude-sandbox’s own devcontainer#
Open this repo (claude-sandbox) in its devcontainer. That’s it — the
sandbox installs itself:
postCreateruns the installer for you, so the shadowclaudeand the global integrity guard are in place the moment the container comes up.The parent directory is mounted at
/workspaces, so all your peer projects sit right there at/workspaces/<project>.Your Claude login and memory persist across rebuilds automatically.
Claude’s network egress is jailed by default — RFC1918 internal hosts and lab devices are blackholed so a compromised session can’t pivot to them, while the internet, DNS, and any
allow-ipdevices stay reachable. This repo’s devcontainer already ships the one required runArg (--device=/dev/net/tun); see Configure the network egress jail to addallow-ipdevices or turn it off.
So to work on any project, just:
cd /workspaces/<your-project>
claude
claude is sandboxed wherever you launch it. By default the writable root
is the directory you launch from, so that project is editable and the
others stay read-only — usually exactly what you want. (To widen it, see
Configure the workspace scope.)
This is the simplest path, especially if your own projects don’t have devcontainers. Skip to Confirm the sandbox to prove it’s working.
The other way: add the sandbox beside your own project#
Already working inside your own project’s devcontainer? Add claude-sandbox next to it.
1. Clone beside your project#
Clone it as a sibling of your project — not inside it — so it lives on the host, survives container rebuilds, and one clone sandboxes every project beside it:
cd .. # the host-mounted parent of your project
git clone https://github.com/gilesknap/claude-sandbox.git
cd claude-sandbox
(This assumes your project’s parent directory is mounted, as it is for
python-copier-template and most DLS devcontainers.)
2. Run the installer#
./install
This relocates the real Claude binary off your PATH and drops a shadow
claude in its place that wraps every invocation in bwrap. It also
installs the global integrity guard and a curated gitconfig. Curious where
everything lands? See What’s installed.
If your host can’t run unprivileged user namespaces, the installer refuses with a specific, actionable diagnostic rather than installing a non-functional sandbox. Fix the reported problem and re-run.
Note: the egress jail needs
/dev/net/tun. By default Claude’s network egress is jailed — a per-process netns that blackholes internal RFC1918 hosts and lab devices so a compromised session can’t pivot to them (see the threat model). The jail is fail-closed: if the container has no/dev/net/tundevice,clauderefuses to launch and tells you so.installapt-installspasst(which providespasta), but it cannot add the runArg for you — that’s adevcontainer.jsonedit. Add"--device=/dev/net/tun"to yourdevcontainer.jsonrunArgsand rebuild (this repo’s own devcontainer already does). If you don’t need lateral isolation, setCLAUDE_SANDBOX_EGRESS_JAIL=0to turn the jail off instead. See Configure the network egress jail.
To restore the sandbox automatically on every rebuild, wire bash <clone>/install into your devcontainer’s postCreate.sh.
3. Run Claude#
claude
Use Claude exactly as you normally would — the shadow on your $PATH wraps
plain claude in the sandbox, nothing else to remember.
Confirm the sandbox#
From inside the Claude session, run:
/verify-sandbox
This runs the 18-check PASS/FAIL battery, and — when the battery passes — follows it with 10 adversarial breakout probes against the live process. It exits non-zero on any FAIL, so the same command doubles as a CI assertion.
A clean run means your host credentials, IDE bridges, and shell environment are isolated from anything Claude reads or runs. If you see a FAIL, stop and resolve it before trusting the session.
Re-run freely after a rebuild#
The installer is idempotent. After a devcontainer rebuild, just run it again
(or let postCreate do it):
./install
The shadow is re-established without re-downloading Claude.
Your statusline script is seeded once and then left alone, so edits you make
to it survive re-runs. If you’d rather a re-run pull the clone’s current
statusline, run STATUS=1 ./install.
Note:
just promote(copying the sandbox’s commands into a workspace so they’re available in place) still exists, but the sibling clone above covers the common case and is the recommended workflow. See Promote a workspace only if you need it.
Next steps#
Persist your login and memory across rebuilds — add a terminal-config mount if your devcontainer doesn’t already have one.
Configure the network egress jail — the jail is on by default; add
allow-iplab devices, satisfy the--device=/dev/net/tunrequirement, or turn it off. It provides lateral (RFC1918) isolation and composes with Claude Code’s nativeallowedDomainsinternet-domain isolation as complementary layers — run both.How-to guides — focused recipes for authenticating with forges, widening writable paths, and more.
Architecture and threat model — why the sandbox is built the way it is, and what it does and doesn’t protect.
Reference — the configuration keys, the integrity battery, and the moving parts, looked up dryly.