Warning
Experimental — not audited, and not for security-sensitive environments. agentbox has not been independently reviewed, and it is under active development. Everything below about isolation, secret masking, and egress control describes design intent, not a guarantee. Do not rely on agentbox to contain untrusted or malicious code, to protect real secrets, or for anything where a sandbox escape would actually matter. See docs/security.md for the threat model and its limits.
Run an agentic coding tool inside an isolated, per-project environment — declarative config, default-deny egress, host-side secret masking, one disposable box per project.
$ cd ~/src/myapp
$ agentbox claudeThat resolves the workspace root, merges configuration, selects a backend satisfying the project's declared isolation floor, builds the project's box image, constructs a masked view of the workspace that hides secrets, starts a default-deny egress proxy, and execs the agent with the working directory mapped to match your shell's.
What this buys you: the blast radius of an agent mistake is reduced to one workspace tree and one egress allowlist, both inspectable beforehand and logged after. That is not the same claim as "safe" — read docs/security.md before running anything unattended.
Coding agents want to read your tree, install packages, and talk to the
network. Running them bare means one prompt injection or one confused agent
can read ~/.ssh, your .env, or push to the wrong remote. Running them in
an ad-hoc container means hand-rolling mounts, network rules, and secret
handling per project — and getting it subtly wrong.
agentbox makes the confined path the zero-config path:
- Zero-setup launch. Works in any directory; a committed
agentbox.tomlmakes it reproducible for the whole team. - Default-deny egress you wrote yourself. The guest has no route to the
outside; the only path is a proxy enforcing an allowlist. There are no
curated bundles: agentbox does not guess which domains a third-party tool
needs, so the list is exactly what your
agentbox.tomlsays.agentbox logs --deniedshows what was refused, which is how you build it. - Secret masking, enforced outside the box.
.agentignorepatterns (gitignore syntax, differentially tested againstgit check-ignore) hide matched files from the guest — reads return empty, writes never reach the host. - One box per project directory.
cdinto a project and you are in its box: nothing to name, nothing to list, nothing to pick. One box takes as many concurrent sessions as you start — run agents in parallel in the same box by runningagentboxin as many terminals as you like. Want a second box, with its own filesystem and its own branch? Make a second directory:git worktree add ../featuregets one for free. - Honest about what it guarantees.
agentbox statusalways names the backend, isolation tier, egress mode and mask mode in force, and every invocation that starts a box prints the same line. - Your image, not agentbox's.
agentbox initwrites anagentbox.dockerfileyour team owns and edits — the toolchain, the agent, whatever this project actually builds with. agentbox installs none of it: no[toolchains], no[agents], nothing to keep up with and nothing that breaks the day you change base image. Already have an image? Point[image].refat it. With neither, agentbox builds a baseline that has anagentuser and a shell and stops there. - Nothing assumed. No defaults layer: every value agentbox acts on is in a
file you own, and a required key that is missing is an error naming it rather
than a number agentbox picked.
agentbox initwrites a complete config once, and after that the file is the whole story. - Any agent. Nothing in the data model is vendor-specific: an agent is a command you install in your image and a set of endpoints you allow.
$ git clone https://github.com/sblmnl/agentbox && cd agentbox && sudo make install
$ cd ~/src/myapp
$ agentbox init # scaffold agentbox.toml + agentbox.dockerfile + .agentignore
$ $EDITOR agentbox.dockerfile # add your toolchain and your agent
$ agentbox up # build the image and start the box
$ agentbox claude # run your agent in the boxUseful from day one:
$ agentbox status # backend, tier, egress, masking, config drift
$ agentbox logs --denied # what did the agent try to reach?
$ agentbox down # stop and remove the box; keep its home
$ agentbox rm # remove the box and everything it holdsAny command that is not one of those six runs inside the box, so
agentbox claude, agentbox make test and agentbox bash -l all work. Use
-- for a command whose name collides: agentbox -- status.
down and rm differ in what survives. down removes the guest and its
networks but keeps the box's persistent home and identity, so agentbox
brings back the same box. rm removes all of it — home, state, and the image
agentbox built for it. An image you pinned yourself with [image].ref is
never removed: agentbox reclaims only what it made.
Requirements: Linux, Docker or rootless Podman for the container tier,
Docker + a Kata runtime + KVM for the vm tier, git, and Go to build. A
single static binary with one dependency (BurntSushi/toml). agentbox is
build-from-source only — there are no published binaries or packages.
Build steps and runtime setup for each tier are in
docs/install.md.
agentbox.toml is committed, reviewable, and layered (user → workspace), with
static validation — unknown keys and backend-scoped keys in the wrong place
are hard errors on every machine, not just where they would apply. There is no
defaults layer beneath those two and no flag layer above them: a required key
nobody set is an error listing every key that is missing, and agentbox init
writes a complete file to start from. Every value the box runs under is in a
file somebody can review.
[image]
dockerfile = "agentbox.dockerfile" # yours; `agentbox init` writes a baseline
[network]
mode = "proxy"
allow = ["api.anthropic.com", "github.com", "registry.npmjs.org"]
[security]
min_isolation = "container"Full reference: docs/configuration.md /
agentbox.toml(5). Copy-paste starting points for common stacks (Node,
Python, Go, .NET, monorepos, air-gapped, VM tier, prebuilt images) are in
docs/configuration-examples.md.
There is no list of toolchains or agents agentbox knows how to install. Such a list is a treadmill: every entry assumes a base image, a package manager and an upstream layout that the next project changes. What a box contains is a Dockerfile question, so it is answered in a Dockerfile:
$ agentbox init # writes agentbox.dockerfile: the baseline, annotated
$ $EDITOR agentbox.dockerfile
$ agentbox --recreate --rebuildagentbox builds that file, keyed by its contents — edit it and the next
invocation rebuilds. Three things are asked of whatever image you point it at:
an agent user at the AGENTBOX_UID/AGENTBOX_GID build args agentbox
passes, nothing you need kept under /home/agent (a persistent volume shadows
it), and whatever your agent needs on PATH. The scaffolded file states them
at the top, and [image].ref skips the build entirely for an image you
already publish.
The workspace layer sits above your user config, so cloning a repository and
running agentbox applies configuration you did not write. agentbox lets it
win — that is what layering means — and then refuses to be quiet about it.
Any workspace value that weakens a security-relevant key relative to a
lower layer produces a warning on every invocation, naming the key, both
values, and the file responsible:
$ agentbox
warning: network.mode: /src/myproject/agentbox.toml lowered this from "proxy"
to "open" (~/.config/agentbox/config.toml set "proxy"); the workspace value
is in effect
agentbox: backend vm (tier vm) | egress open | masking filter, 3 path(s) hiddenThere is no flag that silences this, because there is no flag that reaches the
key: if you disagree with a repository's config, edit your own layer or the
repository's. Tightening is silent, because a project asking for a stronger
box is a project config doing its job. The keys ranked this way are
security.min_isolation, network.mode, security.mask_mode,
workspace.readonly, security.vm.guest_root, security.vm.nested_docker,
and the container hardening booleans. Egress allowlists are not ranked — a
repo declaring the registries it needs is the normal case.
Two isolation tiers exist behind one contract: container (Docker or
rootless Podman) and vm (Kata Containers via Docker, on KVM). They are
not equivalent, and agentbox is built around never letting that
difference hide:
- A project declares the weakest tier it accepts (
min_isolation); if no available backend meets it, agentbox exits 69 naming what is missing. No silent downgrade, no warned downgrade, and no override — there is no flag that lowers the floor. Change the config or fix the host. - Backend-specific settings are scoped (
[security.container],[security.vm]) and statically validated everywhere. status, and the banner printed whenever a box starts, always show the backend and tier in force.
Universal refusals: no host networking, no host PID namespace, no mounted container-runtime socket, no unconfined seccomp. Boxes cannot reach each other's network, state, or mask views.
The full property table, threat model, and documented limitations of
masking are in docs/security.md and
agentbox-security(7). The one-paragraph version: in scope — careless
or prompt-injected agents, malicious install scripts, accidental
destruction outside the workspace; out of scope — kernel/runtime escape
under the container tier, hypervisor escape, determined targeted attackers.
agentbox has never been released. Nothing is published, there is no version number that means anything, and there is no compatibility promise: the CLI and the config schema change whenever a better shape turns up. Do not build anything on top of this yet.
| Area | Status |
|---|---|
CLI contract: dispatch, --, sysexits, signal forwarding, verbatim exit codes |
✅ |
| Identity: workspace detection, one box per workspace root | ✅ |
| Config: 2 layers, static backend-scope validation, loud weakening | ✅ |
| Isolation floor: exit 69, no downgrade, no override | ✅ |
Image: the project's own Dockerfile or [image].ref, builds keyed on file + build args |
✅ |
Config: no defaults layer; required keys reported together, init writes them |
✅ |
Masking: shared gitignore matcher, per-box Layer-0 plan, mask_mode |
✅ |
Network: off/proxy/open, project-written allowlist, deny-first, CONNECT restricted to 443, generated squid config |
✅ |
| State: XDG layout, atomic metadata, per-box locking, per-box homes | ✅ |
| Container backend: Docker + rootless Podman, proxy sidecar, no-gateway network | ✅ |
| VM backend: Kata via Docker, same sidecar topology, host-side mask view | ✅ |
Layer-3 lookup-time filtering (mask_mode = "filter", own share daemon) |
✅ |
Teardown: down keeps the home, rm reclaims only what agentbox built |
✅ |
| Man pages | ✅ |
| Podman for the vm tier | 🚧 blocked upstream (see deviations) |
| Proxy credential injection | 🚧 planned |
Deviations, honestly stated:
- The egress allowlist constrains who, not what, and its edges are coarse.
A CONNECT proxy hands over an opaque TLS tunnel once the target is allowed,
so any hostname sharing a front with an allowed domain is reachable
(verified: a tunnel to
raw.githubusercontent.comcarrying SNI forgist.githubusercontent.comis answered). And an allowed destination is a place data can go —github.comallowed means a repo push is a permitted request. Prefer exact hostnames to wildcards, and read docs/security.md. - The host itself is not behind the proxy on older engines. An
--internalbridge removes the route out but still carries the host's address, so anything bound to0.0.0.0on the host answers from inside a proxy-mode box without passing the proxy (verified with a plain HTTP server). agentbox creates the bridge withgateway_mode_ipv4=isolated, which removes the host address entirely; engines older than Docker 28 do not support it, and there the box is created anyway with a warning on every invocation. - The vm tier is Docker + Kata, and only that. Podman's VM path is
libkrun, whose crun handler implements no
execcallback: a box created under it comes up and no agentbox command can ever enter it (containers/crun#2090). Rather than offer a runtime that parses, selects, creates a box and then cannot be used, agentbox does not offer it — and says so when podman is the only thing installed. There is likewise nohypervisororruntimeconfiguration key: offering a choice agentbox cannot honor produces exactly that failure. - A Kata guest cannot use Docker's embedded DNS. The resolver lives at
127.0.0.11 inside the network namespace, and a VM guest's loopback is its
own, so container names do not resolve. The vm tier therefore addresses
its proxy sidecar by a pinned IP, and
network = "open"bind-mounts a generated/etc/resolv.confcarrying the host's real upstream nameservers. (--dnsdoes not help: on a user-defined network the engine still writes 127.0.0.11 and merely redirects the embedded resolver's upstream.) Inproxymode the guest needs no resolver at all — squid resolves on its behalf, which also denies the box a DNS channel of its own. - The Layer-3 filtered share is delivered by interposing agentbox's own
FUSE filesystem underneath the runtime's virtiofsd (virtiofsd serves
the filtered mount), not by replacing virtiofsd via Kata's
virtio_fs_daemon, the more obvious integration point. The enforcement point is identical — host-side, outside guest reach — and the seam does not depend on runtime-specific daemon-path configuration. - The filtered share needs
/dev/fuseand a mount route: agentbox running as root, or the setuidfusermount3helper withuser_allow_otherset in/etc/fuse.conf, which lets the share daemon run entirely in the user's context. Where no route exists,mask_mode = "auto"resolves toviewwith a warning naming the reason, and an explicit"filter"is an error. No silent downgrade occurs in either direction. - The
vmtier's host-side mask view needs agentbox to run with CAP_SYS_ADMIN; without it, mask mounts are expressed in the guest and agentbox warns that a guest root process could unmount them. - A committed workspace config can weaken your security settings; agentbox warns on every invocation rather than refusing. See "A committed config outranks yours" above.
- VM conformance tests need KVM plus a Kata runtime and run as a separate
CI job that fails if the suite skips; they are not part of
go test ./...on an ordinary host. - Container-backend end-to-end tests need a Docker daemon and are not part
of
go test ./...; the policy, plan, and state logic they exercise is covered at the unit level.
$ go build ./cmd/agentbox # Go 1.26, one dependency
$ go test ./...
$ make all # fmt-check + vet + build + testThe ignore-matcher conformance suite differentially tests against
git check-ignore and needs git on PATH. The real-mount Layer-0 test
requires root or CAP_SYS_ADMIN and skips visibly elsewhere; CI runs it in
a privileged job.
See CONTRIBUTING.md. Security reports: see SECURITY.md — please do not open public issues for vulnerabilities.
Documented behavior is the contract; behavior changes should update the docs, and divergences belong in the "deviations" list above, not in silence.
The Unlicense — this software is released into the public domain.