1165aea9aa
Setting --hostname sandbox inside the UTS namespace was purely cosmetic (a label for interactive shells) and provided no isolation beyond what --unshare-uts already gives. It also caused codex to hang for ~5s on startup: glibc resolved the unknown "sandbox" name via systemd-resolved and waited through two DNS retry timeouts before giving up. Dropping the override lets the sandbox inherit the host's hostname, which already resolves locally, eliminating the stall.
83 lines
5.4 KiB
Markdown
83 lines
5.4 KiB
Markdown
# agent-sandbox
|
|
|
|
Sandbox agentic coding assistants with [bubblewrap](https://github.com/containers/bubblewrap). Limits what an AI agent can see and modify on the host, reducing the blast radius of prompt injection and accidental damage.
|
|
|
|
## Modes
|
|
|
|
### Whitelist
|
|
|
|
Tight sandbox for normal agent coding tasks. Only explicitly listed paths are visible — system binaries, libraries, a subset of `/etc`, `/sys` (all read-only), synthetic `/dev`, private `/proc`, `/tmp`, `/run`, and the working directory (read-write). Everything else is invisible. Whitelist always runs with hardening on (see below).
|
|
|
|
### Blacklist
|
|
|
|
Looser sandbox for system-level debugging with agent assistance. The host filesystem is mounted read-only, with targeted overlays hiding sensitive paths (credentials, history, secrets, sockets, input devices). `/run` and `${XDG_RUNTIME_DIR}` are replaced with tmpfs mounts that only expose the paths needed for system tooling (`systemctl`, `resolvectl`, `journalctl`, etc.).
|
|
|
|
The threat model is prompt injection and accidental damage, not a determined attacker with user-level access.
|
|
|
|
**Not protected in blacklist mode:** arbitrary readable files outside the sensitive paths list, and D-Bus method calls (access control is daemon-side).
|
|
|
|
### Hardening
|
|
|
|
`--hardened` unshares the IPC, PID, and UTS namespaces. This is independent of the filesystem mode and can be combined with either. Whitelist mode enables it unconditionally; blacklist mode leaves it off by default. Use `--no-hardened` to override a config-file `hardened = true` (note: it cannot disable the implicit hardening that comes with whitelist mode).
|
|
|
|
### Network
|
|
|
|
The network namespace is shared with the host by default. Pass `--unshare-net` (or set `unshare-net = true` in the config) to isolate it; `--share-net` overrides a config-file `unshare-net = true`.
|
|
|
|
## Environment filtering
|
|
|
|
Both modes clamp the environment the child sees so prompt-injected agents can't `printenv` their way to secrets.
|
|
|
|
- **Whitelist** clears the parent env and re-adds a small allowlist: identity/shell vars (`HOME`, `PATH`, …), terminal/locale, proxy, non-GUI XDG base dirs, and agent vendor prefixes (`ANTHROPIC_*`, `CLAUDE_*`, `OPENAI_*`, `CODEX_*`, `GEMINI_*`, `OTEL_*`).
|
|
- **Blacklist** keeps the parent env but unsets credentials and dangling pointers: cloud creds (`AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS`, …), backup tool passphrases, sockets stripped by path overlays (`SSH_AUTH_SOCK`, `DISPLAY`, `GNUPGHOME`, …), and anything matching `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, `*_PASSPHRASE`, `*_API_KEY`, `*_PRIVATE_KEY`, `*_CLIENT_SECRET`. Vendor-prefix vars (`ANTHROPIC_API_KEY` etc.) are carved out so they survive.
|
|
|
|
Disable the built-in policy entirely with `--no-env-filter` (or `env-filter = false` in the config file) to pass the parent env through unchanged. User `--setenv`/`--unsetenv` escape hatches still apply.
|
|
|
|
## Seccomp
|
|
|
|
Both modes apply a seccomp-BPF syscall allowlist derived from Podman's default profile. Dangerous syscalls (`mount`, `unshare`, `ptrace`, `bpf`, `perf_event_open`, `io_uring_*`, `keyctl`, `kexec_*`, …) return `ENOSYS`. Disable with `--no-seccomp` or `seccomp = false` in the config file.
|
|
|
|
## Configuration file
|
|
|
|
Settings can be stored in a TOML config file at `$XDG_CONFIG_HOME/agent-sandbox/config.toml` (or pass `--config <path>`). Use `--no-config` to skip loading it. The config file accepts the same options as the corresponding CLI flags. `config-example.toml` in the repo root is a fully commented example — symlink it into place to use it directly:
|
|
|
|
```bash
|
|
ln -sf "$(pwd)/config-example.toml" "${XDG_CONFIG_HOME:-$HOME/.config}/agent-sandbox/config.toml"
|
|
```
|
|
|
|
Top-level keys set defaults; `[profile.<name>]` sections define named presets selectable with `--profile <name>`. CLI flags always take highest precedence, followed by the active profile, then top-level defaults. When a profile is active, list-valued fields (`ro`, `rw`, `mask`, `env`, `unsetenv`, `bwrap-args`) append to the globals; scalar fields (modes, flags, `entrypoint`, `command`, `chdir`) replace them.
|
|
|
|
```toml
|
|
# Global defaults
|
|
whitelist = true
|
|
unshare-net = true
|
|
ro = ["~/.aws"]
|
|
|
|
# Named profile
|
|
[profile.docker]
|
|
blacklist = true
|
|
rw = ["/var/run/docker.sock"]
|
|
command = ["claude", "--dangerously-skip-permissions"]
|
|
```
|
|
|
|
## Escape hatches
|
|
|
|
When the agent needs access to something the sandbox blocks, use `--rw` or `--ro` for paths and `--setenv`/`--unsetenv` for env vars. User overrides always win over the built-in policies.
|
|
|
|
```bash
|
|
agent-sandbox --rw /var/run/docker.sock -- claude --dangerously-skip-permissions
|
|
agent-sandbox --ro ~/.aws -- claude --dangerously-skip-permissions
|
|
agent-sandbox --setenv DATABASE_URL=postgres://localhost/dev -- claude
|
|
agent-sandbox --unsetenv HTTP_PROXY -- claude
|
|
```
|
|
|
|
## Ubuntu 23.10+: AppArmor unprivileged userns restrictions
|
|
|
|
Ubuntu 23.10 and later ship with `kernel.apparmor_restrict_unprivileged_userns=1`, which blocks `bwrap` from creating user namespaces and causes failures like `bwrap: setting up uid map: Permission denied`. Ubuntu does not enable a profile for `bwrap` by default, but the `apparmor-profiles` package ships an opt-in `bwrap-userns-restrict` profile that grants the necessary access while still preventing `bwrap` from being used to bypass the userns restriction generally:
|
|
|
|
```bash
|
|
sudo apt install apparmor-profiles
|
|
sudo ln -s /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/
|
|
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
|
|
```
|