Add README
This commit is contained in:
@@ -1,3 +1,85 @@
|
||||
# agent-sandbox
|
||||
|
||||
Rootless podman sandbox for Claude Code with allowlisted egress
|
||||
Runs Claude Code in a rootless podman sandbox. The only thing visible from the
|
||||
host is the directory you launch it from, and the only way out of the sandbox
|
||||
is a per-session router that serves the hostnames on your allowlist.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
cd ~/src/some-repo
|
||||
agent-sandbox [claude args...] # e.g. agent-sandbox --continue
|
||||
agent-sandbox --exec bash # a shell in the sandbox instead of claude
|
||||
```
|
||||
|
||||
- `$PWD` is mounted read-write at the same path. Files you create there are
|
||||
owned by your host user. Nothing else from the host is mounted, and SELinux
|
||||
labels on your files are never changed (`label=disable`).
|
||||
- Claude's home is the `agent-sandbox-home` podman volume (login, config and
|
||||
nested container images live here). All sessions share it.
|
||||
- `podman` works inside the sandbox (nested rootless). The host podman socket is
|
||||
never exposed.
|
||||
- You can run several sessions at once, from different directories.
|
||||
- Environment overrides: `AGENT_SANDBOX_IMAGE`, `AGENT_SANDBOX_ROUTER_IMAGE`,
|
||||
`AGENT_SANDBOX_ALLOWLIST`, `AGENT_SANDBOX_HOME_VOLUME`.
|
||||
|
||||
## Allowlist
|
||||
|
||||
`~/.config/agent-sandbox/allowed-hosts` is copied from the packaged default on
|
||||
first run. It is read at the start of each session.
|
||||
|
||||
```
|
||||
api.anthropic.com exactly this host (ports 80 and 443)
|
||||
.example.com example.com and every subdomain (ports 80 and 443)
|
||||
db.example.com:5432 this exact host on TCP 5432 too (ssh, postgres, ...)
|
||||
```
|
||||
|
||||
You don't need a proxy setting. Allowlisted names resolve to loopback
|
||||
addresses inside the sandbox, and all other names return NXDOMAIN.
|
||||
|
||||
## How it works
|
||||
|
||||
Each session gets its own `--internal` podman network with no route out, and a
|
||||
pod on that network that holds the sandbox's netns, DNS and hosts file.
|
||||
|
||||
- **router** (on the internal net and the shared egress net): dnsmasq answers
|
||||
for allowlisted names only and never forwards a query. nginx routes 443 by
|
||||
SNI and 80 by Host header to allowlisted names only. Each `host:port` entry
|
||||
has its own router port, which forwards to that host and port only.
|
||||
- **forwarder** (in the pod, read-only, no capabilities except
|
||||
NET_BIND_SERVICE): socat listeners on the loopback IPs that forward to the
|
||||
router.
|
||||
- **claude** (in the pod, non-root keep-id user, no setuid binaries, no
|
||||
NET_ADMIN, NET_RAW or SYS_ADMIN): has no capability over the netns. It can't
|
||||
change addresses, routes, nftables, `/etc/hosts` or `/etc/resolv.conf`, and
|
||||
can't see or signal the forwarders.
|
||||
- Nested containers share claude's netns by default. They get the same DNS and
|
||||
forwarders, and they can't change the netns either. A nested container on its
|
||||
own network (`--network pasta|bridge`) has no egress.
|
||||
|
||||
## Threat model
|
||||
|
||||
- Confined: direct IP egress, other hostnames, DNS exfiltration (dnsmasq never
|
||||
forwards), SNI or Host spoofing (the destination is always the name the
|
||||
client sent, and it must be on the allowlist), the host filesystem beyond
|
||||
`$PWD`, and the host podman socket.
|
||||
- Not confined: anything you allowlist is still an exfiltration channel.
|
||||
`git.unkin.net` and `api.anthropic.com` can carry your data out. `$PWD` is
|
||||
writable, so anything there, including `.git/hooks`, runs later on the host
|
||||
if you trust it.
|
||||
- Router-side DNS resolves allowlisted names on the host's resolvers, so
|
||||
internal names like `vault.service.consul` work.
|
||||
- The home volume persists across sessions and is shared by all of them.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
make build # local images (localhost/agent-sandbox{,-router}:dev)
|
||||
make test lint pre-commit
|
||||
make rpm # dist/agent-sandbox-*.rpm
|
||||
make patch|minor|major
|
||||
```
|
||||
|
||||
The in-repo launcher runs the local `:dev` images. The RPM's launcher pins the
|
||||
images published by the release pipeline for its tag:
|
||||
`artifactapi.k8s.syd1.au.unkin.net/docker-internal/agent-sandbox{,-router}`.
|
||||
|
||||
Reference in New Issue
Block a user