Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

Claude Code sandbox (Docker)

Two switches, controlled independently:

  1. Filesystem — always locked to exactly one mounted folder, in both modes. run.sh bind-mounts only the workspace path you pass it (-v "$WORKSPACE:/workspace"). Nothing else on your machine is mounted, so the container simply cannot see any other project, even if Claude tries cd ../other-project or cd ~ — those paths don't exist inside the container's filesystem at all. This holds true whether the network is locked or open; it's a property of the Docker mount namespace, not the firewall.

  2. Network — toggled by MODE:

    • locked (default): firewall.sh runs at container start (via a scoped, passwordless sudo entry that can run only that one script) and drops all outbound traffic except DNS, loopback, the Docker host bridge, and Claude's own domains (api.anthropic.com, claude.ai, platform.claude.com). Everything else — npm, pip, GitHub, curl to random URLs — is rejected.
    • open: firewall.sh never runs; the container gets whatever network access the Docker host normally provides.

Packages: install at build time, not run time

Per your requirement, locked mode allows only Claude's own domains — no npm/pip registries. That means npm install / pip install will fail once the container is running in locked mode. Install everything you need in the Dockerfile instead (it has full network access during docker build, before the firewall ever applies):

RUN npm install -g @anthropic-ai/claude-code@latest
# COPY package*.json /tmp/deps/ && RUN cd /tmp/deps && npm ci
# RUN pip install --break-system-packages -r requirements.txt

If a task genuinely needs a fresh package mid-session, run that one session in MODE=open instead, or rebuild the image with the new dependency added and go back to locked.

Usage

Every run picks a mode (locked/open) and, optionally, what runs inside: claude (default), shell (plain bash, no Claude at all), or any literal command. The folder-only mount and the network restriction apply identically no matter which one you pick — shell mode is the exact same sandbox, just with you typing the commands instead of Claude.

Browser login (no API key needed), running Claude Code:

./run.sh locked /absolute/path/to/project-a

On first start, Claude Code prints a login URL/code — open it in your own browser, approve it, and you're done. Credentials are saved to a named Docker volume (claude-sandbox-config), so this only happens once; later runs (even against a different workspace path) reuse it. To wipe it and force a fresh login: docker volume rm claude-sandbox-config.

API key instead (skips the login prompt):

export ANTHROPIC_API_KEY=sk-ant-...
./run.sh locked /absolute/path/to/project-a

Full internet, still only this one folder mounted:

./run.sh open /absolute/path/to/project-a

Plain shell instead of Claude — same sandbox, you're driving:

./run.sh locked /absolute/path/to/project-a shell
./run.sh open   /absolute/path/to/project-a shell

You land in bash at /workspace with the same folder-only mount and the same network restriction Claude would have had. No login needed for this one. Exit the shell (exit or Ctrl-D) and the container is removed (--rm), same as after a Claude session.

Swap in a different path and neither mode can reach project-b sitting next to it on your disk — it was never mounted, so it isn't there. And remember you don't need the container at all just to edit files: the mount is live in both directions, so anything you change in Finder or your normal editor on the Mac shows up inside the container immediately, and vice versa, whether or not anything is currently running inside it.

Notes / things to double check before relying on this

  • Auth: firewall.sh allowlists claude.ai and platform.claude.com by default (needed for browser-based claude login), on top of api.anthropic.com. If you only ever use ANTHROPIC_API_KEY, you can remove those two lines to shrink the allowlist further.
  • Login persistence: run.sh mounts a named volume, claude-sandbox-config, at /home/sandbox/.claude in every container, so a browser login survives container restarts. It's shared across whatever workspace path you point run.sh at — that's intentional (same login, different projects) but means anyone who can run this script also reuses that login. Remove the volume mount in run.sh if you want fully separate credentials per workspace instead.
  • Docker Desktop on macOS: containers run inside Docker's own Linux VM, so HOST_NETWORK in firewall.sh refers to Docker's internal bridge, not your Mac's LAN. That's expected and fine.
  • Capabilities: run.sh deliberately does not --cap-drop=ALL. sudo (used by firewall.sh) needs several of Docker's normal default capabilities (SETUID/SETGID/AUDIT_WRITE) just to switch to root and log it -- stripping them produces a confusing sudo: unable to change to root gid failure with no obvious cause. So this setup keeps Docker's ordinary default capability set (already far short of real root) and only adds NET_ADMIN/NET_RAW on top, and only in locked mode, for iptables/ipset. open mode and shell mode add nothing extra.
  • Verify it actually holds: firewall.sh self-tests on start (fails the container if https://example.com is still reachable in locked mode). Worth also manually trying curl https://google.com and ls / vs a path outside /workspace from inside a locked container once, to see the rejections yourself. sandbox-test-project/verify.sh (sibling folder) automates exactly this.
  • This is a reference implementation you should treat as a starting point, not a hardened security boundary — Docker's default bridge network, a misconfigured HOST_NETWORK match, or DNS-based bypass tricks (e.g. DNS rebinding against the allowed IP) are the usual soft spots in this pattern. For anything higher-stakes than "keep a coding assistant from wandering into the wrong folder or the open internet," pair this with a proper outbound proxy allowlist rather than iptables+ipset alone.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages