Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

aibox runs Claude Code in a small virtual machine on your computer.

Claude Code can read and change files, run commands and access the internet. That is what makes it useful, but it also means a bad prompt or a malicious file in a repository can make it do things you did not want. aibox limits the damage. Claude Code only sees the files you give it, and it can only connect to hosts you allow.

You can use aibox in two ways:

  • aibox run starts Claude Code in your project folder, like you would on your machine. Changes show up in the folder right away. See Running Claude Code.
  • aibox task hands Claude Code a job to do on its own. It works on a clone of the last commit and you get its commits back to review. The VM is deleted afterwards. See Tasks.

You can also mount other folders into the VM, read-only.

With aibox run, the VM connects to your machine like this:

%%{init: {"flowchart": {"nodeSpacing": 60}}}%%
flowchart LR
  subgraph vm["VM"]
    claude["<br><br>Claude Code<br><br>"]
  end
  subgraph machine["Your machine"]
    terminal["Your terminal"]
    project["Project folder"]
    mounts["Mounts and skills"]
    subgraph aibox["aibox"]
      proxy["Proxy"]
      broker["Git broker"]
    end
  end
  hosts["Hosts on the allow list"]
  repos["Repositories on the git list"]

  claude <-->|"SSH over vsock"| terminal
  claude <-->|"virtio-fs, 9p on Windows"| project
  claude ---|"the same, read-only"| mounts
  claude <-->|"vsock"| proxy
  proxy ---> hosts
  proxy --> broker
  broker -->|"your git login"| repos

The VM has no network card. vsock connects it to your terminal and to a proxy in aibox. On a Linux kernel without vsock namespaces, a serial port does that. Apart from that, it only shares folders with your machine. With git in the config, a broker adds your git login to the VM’s git requests, so the login never enters the VM.

aibox runs on Linux with QEMU, on macOS with Virtualization.framework and on Windows with Hyper-V. The VM boots in about a second.

Why a VM

There are other ways to fence in Claude Code. Here is why aibox uses a VM instead.

Claude Code has a sandbox of its own. It’s quick to turn on and good for cutting down on permission prompts. But it only covers shell commands. Claude Code’s file tools, MCP servers and hooks still run directly on your machine. Anthropic’s sandbox runtime wraps the whole process, but it still shares your kernel, and you configure the paths and hosts yourself.

A Docker container, like Anthropic’s dev container, puts the whole process in a box. But containers share the kernel with your machine, so a kernel bug is a way out. The dev container’s firewall runs inside the same container as Claude Code, and it allows the IP addresses it looked up when the container started. On a Mac, Docker Desktop runs all containers in one shared Linux VM.

A VM has its own kernel, so a kernel bug alone is not enough to get out. Something has to break the hypervisor or the programs that serve the VM. aibox keeps that VM small: no network card, no kernel modules, and a proxy on your machine as its only way to the network. It also locks down the programs that run the VM on your machine.

Apple’s container tool also runs each container in its own VM. But aibox can’t lock down its runtime, and with it aibox would have to reach the VM through a socket that other programs of yours could reach too. So on macOS aibox runs the VM with Virtualization.framework itself, like it runs QEMU on Linux.

Docker Sandboxes is the closest to aibox. It also runs agents in a microVM, with a proxy that only lets allowed hosts through and keeps API keys on your machine. You need to sign in to Docker to use it. aibox needs no account, and it has a git broker that only lets pushes through to the branches you name.

What aibox does not do

aibox keeps Claude Code away from the rest of your machine. It does not check what Claude Code does with the things you give it. With aibox run the VM can change anything in your project folder, .git included, and a task can use every host and secret you allow it. Read What the VM writes, Security and the Threat model to see where the limits are.

Installing

Homebrew

On a Mac with Apple silicon or on Linux on amd64, install aibox with Homebrew:

brew install --cask the127/tap/aibox

On Linux, Homebrew installs just the binary. Install QEMU, virtiofsd and bubblewrap from your distribution, not from Homebrew. Ubuntu only lets its own bubblewrap make the namespaces that the sandbox needs.

sudo apt install qemu-system-x86 virtiofsd bubblewrap        # Debian, Ubuntu
sudo dnf install qemu-system-x86-core virtiofsd bubblewrap   # Fedora

aibox takes the first of these programs on your PATH, and Homebrew puts its own folder first. If you installed any of them with Homebrew, uninstall them there.

The rest of Linux applies too.

Linux

Each release has a deb and an rpm for amd64. They pull in QEMU, virtiofsd and bubblewrap. There is also a tar.gz with just the binary, and then you install those three yourself.

aibox needs:

  • Linux 6.7 or newer. That’s the first Linux whose Landlock can limit aibox’s network connections.
  • access to /dev/kvm
  • virtiofsd on your PATH or at /usr/libexec/virtiofsd, which is where Debian 13, Ubuntu 24.04 and Fedora put it

QEMU and virtiofsd from Nix work too.

On Linux 7.0 or newer with vsock (CONFIG_VSOCKETS and CONFIG_VHOST_VSOCK), the VM reaches aibox over vsock. Most distributions have vsock as a module. aibox opens /dev/vhost-vsock to make the kernel load it. If that doesn’t work, load it with sudo modprobe vhost_vsock. On an older kernel, or one without vsock, the VM reaches aibox over a virtio serial port instead. The first line of console.log then says why. When loading vsock would give the VM vsock, aibox also tells you in the terminal. See The network.

Windows

aibox runs on Windows 11 on amd64 with Hyper-V, which Windows 11 Pro, Enterprise and Education have. Download aibox.exe from the releases.

Hyper-V is off until you turn it on. Do that once, from a PowerShell you opened as administrator, and restart Windows:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

aibox service install tells you when Hyper-V is off, and so does aibox run when the service can’t make a VM because of it. If you turn Hyper-V off later, the service no longer starts: run aibox service install again to see why.

Hyper-V only lets administrators start VMs, so aibox comes with a Windows service that starts them for you. Install it once, from a terminal you opened as administrator:

aibox service install

That downloads the VM image, puts the service under C:\Program Files\aibox and its kernel and root disk under C:\ProgramData\aibox, and starts it. From then on you run aibox run and aibox task as yourself, without administrator rights. Run aibox service install again after you update aibox: aibox runs only with a service of its own version, since the service decides how the VM is made. Each aibox run and aibox task checks that before it makes a VM, and says so when they differ. Run aibox service uninstall to remove it all.

Nix

The flake in this repository has a package of the latest release, for Linux on amd64 and macOS on Apple silicon. On Linux it brings QEMU, virtiofsd and bubblewrap with it.

nix profile install github:The127/aibox

You can also run it without installing:

nix run github:The127/aibox -- run

The VM image

Our packages don’t include the VM image. The first aibox run downloads the image for its release, which is about 150 MB, and keeps it in ~/.aibox/image/<version>. aibox only takes the image whose SHA-256 was built into the release. After an upgrade it downloads the new image and removes the old one.

Packaging aibox

If you package aibox, you can ship the VM image with it. Then your users don’t download it, and your package manager updates it together with aibox.

Put vmlinuz and os.ext4 from the release’s aibox-image_<arch>.tar.gz into a folder that all users can read. Our release binaries look in /usr/lib/aibox on Linux and in /opt/homebrew/lib/aibox on macOS. If you build aibox yourself, set the folder at build time:

go build -ldflags "-X github.com/the127/aibox/internal/image.systemDir=/usr/lib/aibox" ./cmd/aibox

When that folder has both files, aibox uses them and downloads nothing. If the folder is there but a file is missing, aibox says so and downloads the image instead. --image still takes precedence.

Ship the image of the same release as the binary. aibox checks the kernel against the SHA-256 built into the release and refuses to start with any other. If you build aibox yourself, build in the SHA-256 of each kernel too, or aibox boots any kernel:

go build -ldflags "-X github.com/the127/aibox/internal/image.kernelDigests=amd64:<sha256>,arm64:<sha256>" ./cmd/aibox

Note that aibox doesn’t check the root disk yet.

Checking a download

Each release has a checksums.txt with the checksums of all its files. It is signed with cosign, without a key. To check a download, run this in the folder you downloaded it to:

cosign verify-blob checksums.txt \
  --bundle checksums.txt.sigstore.json \
  --certificate-identity-regexp '^https://github.com/The127/aibox/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum --ignore-missing -c checksums.txt

The first command checks that the release workflow of this repository signed checksums.txt. The second checks your files against it.

Running Claude Code

Go to your project folder, let aibox suggest the hosts its tools need, and start it:

cd ~/projects/my-app
aibox config suggest
aibox run

The VM only reaches the hosts you allow. aibox config suggest looks at the files in your project, like go.mod or package.json, and offers the presets for them. It asks before it adds them.

aibox run boots the VM, mounts the project at /project and starts Claude Code there. Log in like you would on your machine. aibox keeps the login for the next run in this project.

Each project has a VM home of its own, so you log in once for each project. aibox doesn’t copy the login from one project to another. That would mean aibox handles your login, and Anthropic’s terms don’t allow anyone but you to do that.

aibox won’t start in your home folder or above it, because the VM would see all your files.

FlagWhat it does
--shellOpens a shell in the VM instead of Claude Code.
--resume <id>Goes on with the Claude Code session that has this ID. When you quit Claude Code, aibox prints the command to go on with the session you were in.
--memorySets the VM’s memory in MiB. The default is 2048, or what the config says.
--cpusSets the VM’s number of CPUs. The default is 2, or what the config says.
--imageBoots a folder with vmlinuz and os.ext4 instead of the image your package installed or aibox downloaded. aibox doesn’t check its kernel.
--no-sandboxRuns QEMU without its bubblewrap sandbox, for debugging. Linux only.

What aibox keeps for a project

Each project gets a folder in ~/.aibox/projects, named after the project’s path. Letters and digits stay, each / becomes -, and every other character becomes _ and a code, so /work/my-app gets -work-my_2dapp. A very long name is cut and ends in a hash of the path. The folder holds:

FileWhat’s in it
config.yamlThe project config.
home/The VM’s home folder, so Claude Code’s login and ~/.claude survive restarts. Other projects don’t see them.
state.ext4A disk for the VM’s /usr/local and ~/.cache.
console.logThe kernel and init messages of the last run.
proxy.logEvery host and port the proxy connected to or refused, the first time in each run. When the run ends, a summary says how often and when last. A killed aibox writes no summary.
tasks/The project’s tasks.
pathThe project’s path, for aibox projects list.
run.lock, tasks.lockLocked while the project runs or a task runs, so that a reset or a remove leaves it alone.

Tools you install into /usr/local in the VM stay on the state disk. They survive restarts and new images. Caches with lots of small files, like Go’s build cache or pip’s, live there too. The shared home folder would be slow for them.

The disk only takes up space on your machine as it fills, up to the size you set with disk. The VM formats it on the first boot. It won’t format a disk that has data on it but no ext4 file system, so a broken disk never gets wiped.

Starting over

The VM’s home folder and state disk stay between runs. That’s how your login and your tools survive. But it also means a session can leave something behind for the next one. That might be a broken tool or a full disk, or a line in ~/.claude/CLAUDE.md that something in the project slipped in. To start the VM over, run this in the project folder:

aibox projects reset

This clears the VM’s home folder and deletes its state disk. The next run makes a new disk, at the size disk says. Your config, the logs and the tasks stay, and so does Claude Code’s login. Claude Code asks for your color theme again, since its other settings are gone. Your old sessions are gone too, so --resume can’t go back to them.

If you think a session was steered, add --logout. Then the login goes too. The VM can write the login file, so a steered session could have put someone else’s login there, and your next sessions would work in their account.

aibox asks before it changes anything. Without a terminal, pass --yes. It refuses while the project runs.

Removing a project

aibox never removes a project’s folder on its own. To see the projects it keeps a folder for, run:

aibox projects list

To remove everything aibox keeps for a project, run this in the project folder, or pass the project’s path:

aibox projects remove
aibox projects remove ~/projects/old-app

That removes the config, the VM’s home folder with the login, the state disk, the logs and the tasks. Your project folder itself stays as it is. This works for a project whose folder you already moved or deleted too. aibox asks first, and refuses while the project or one of its tasks runs.

In your IDE

Claude Code can work with your IDE, like VS Code or GoLand: it sees the code you select, shows its changes as a diff in the editor and reads the errors the IDE reports. On your machine the IDE’s Claude Code extension or plugin does this through a server that only listens on your machine. The VM can’t reach it, and it shouldn’t, because that server also runs code and opens files anywhere.

aibox can link the two for you. It’s off until you turn it on in the project config:

ide: true

Then open the project in your IDE and start aibox run in its terminal. Claude Code in the VM connects to the IDE by itself. /ide shows it as connected. aibox only passes on what the IDE integration needs, and only for files in the project:

  • the code you select in a project file
  • diffs of project files in the editor
  • the errors the IDE reports for project files

Everything else stays out. Claude Code can’t run code through the IDE, and it doesn’t see what you select in files outside the project.

Note that aibox looks for the IDE when the VM starts. If you open the IDE later or restart it, start aibox run again. Also, the IDE talks to one Claude Code at a time. Claude Code on your machine and the one in the VM take the link from each other.

Approve Claude Code’s changes in the terminal, and don’t type into its diff in the IDE. When you accept or edit a diff in the IDE, the IDE writes the file itself, and the VM could have turned it into a link to a file outside the project. See What aibox does not protect.

aibox has been tested with VS Code and GoLand. Other JetBrains IDEs use the same plugin and should work too.

How it works

The IDE extension writes a file to ~/.claude/ide with its port and a token. aibox looks there for an IDE that has the project open. It writes a file like that into the VM’s ~/.claude/ide, with a token of its own and port 3131, and sets CLAUDE_CODE_SSE_PORT so that Claude Code connects by itself. The init forwards port 3131 to the proxy, and the proxy hands the connection to aibox’s IDE bridge. The bridge connects to the IDE with the IDE’s token, which never gets into the VM.

The bridge reads every message and passes on only the ones it knows, with /project in paths turned into the project’s path on your machine and back. It refuses paths outside the project, also through symlinks, and gives each request an ID of its own. proxy.log shows what it held back.

Pasting images

Press Ctrl+V in Claude Code to paste the image on your clipboard, like a screenshot. Pasting text works like in any terminal, and so does copying.

On Windows, press Alt+V, as in Claude Code on Windows. Windows Terminal and the Windows console paste on Ctrl+V themselves, so the key never reaches aibox. aibox passes Alt+V on to the VM as Ctrl+V, so Alt+V in another program in the VM is Ctrl+V there too.

The VM can’t read your clipboard whenever it likes. If it could, it would see every password you copy. aibox lets it read the clipboard only right after you press Ctrl+V, for 5 seconds, and only one image. If you don’t want that either, set clipboard: false in the project config.

Note that aibox watches for the key, not for Claude Code. Ctrl+V in another program in the VM, like bash or vim, lets the VM read the clipboard too. If you bind image paste in Claude Code to another key, aibox doesn’t see it and pasting images stops working.

On Linux aibox reads the clipboard with wl-paste under Wayland and with xclip under X. Install the one you need on your machine. On macOS it uses osascript.

How it works

aibox reads what you type before it goes to the VM. It looks for Ctrl+V as the terminal sends it: as a control character, in the kitty keyboard protocol or with xterm’s modifyOtherKeys. Text you paste into the terminal doesn’t count. On Windows it looks for Alt+V the same ways, and sends the VM Ctrl+V in its place. It does that only when Alt+V comes in one read, as it does from a terminal: holding back the escape it starts with until the next read would hold back the Escape key too.

In the VM, xclip and wl-paste are aibox’s init under another name. When Claude Code runs them for Ctrl+V, they ask aibox on port 3132 of the loopback. The first asks which types the clipboard holds, the second asks for the image. They answer nothing else, so Claude Code can’t read text from your clipboard.

Once the VM runs, aibox can’t start programs any more. So before the VM starts, aibox starts a helper, a second aibox. The helper reads the clipboard when aibox asks and does nothing else. It ends with aibox.

On macOS

The VM runs arm64 Linux, so Mac programs don’t run in it. If you mount a tool from your Mac, like a Go SDK or /nix/store, the VM can’t run it. Get arm64 Linux builds of your tools instead, or install them into /usr/local in the VM, where they stay.

You can only run one aibox run per project at a time on macOS. Virtualization.framework locks the state disk while the VM runs.

On Windows

The VM runs on Hyper-V. The service of aibox starts it for you, because Hyper-V only lets administrators do that; aibox run itself needs no administrator rights. Windows programs don’t run in the VM, which runs amd64 Linux.

You can only run one aibox run per project at a time on Windows too. Hyper-V keeps the state disk open while the VM runs.

The project’s folder in ~/.aibox/projects is named after the path as Windows spells it, so cd c:\users\me\repo and cd C:\Users\me\repo are the same project. A junction, a symlink, a subst drive or a mapped drive leads to the project it points to; on a mapped drive Z:\repo that’s \\server\share\repo. That’s unlike Linux and macOS, where a project you reach through a symlink is a project of its own.

The state disk, state.ext4, is a dynamic VHD on Windows, the kind of disk Hyper-V attaches, which takes up space as the VM fills it. The VM sees the same disk as on Linux.

The VM sees your folders as Windows keeps them. Names are the same in any case, so Foo and foo are one file. Files made on Windows are not executable in the VM until you chmod +x them there, and a checkout of git for Windows has CRLF line endings, which bash scripts in the VM can’t run with. Tools on your machine are Windows programs, which the VM can’t run, so a mount of them is no use. Install the tools in the VM into /usr/local instead: it’s on the state disk and stays.

How it works

aibox asks the service over a named pipe to make a VM with the memory, the processors and the kernel command line it wants. The service checks them against what the machine has and boots its own copy of the kernel and the root disk. It never opens a folder or a file you name: it has Windows tell it who you are, and gives the VM the state disk you hold open, and nothing else of yours.

Your project, home and mounts are served to the VM by a helper, a copy of aibox, over 9p on Hyper-V sockets, as you: the VM can do with them what you can, and no more. The terminal and the proxy go over the same sockets. aibox serves the terminal itself, and the proxy from another helper in an AppContainer. That helper hands the requests for the services of the proxy, such as the Claude API forwarder and the git broker, to aibox, which holds their keys, over a port of the loopback. The service lets the helper reach only that port and those of the allow list. Both helpers end with aibox. The console comes over a pipe the service makes for the VM and hands to aibox open: Hyper-V connects the VM to it, and no one can open it by name. When aibox ends, or is killed, the service ends the VM.

How it works

The terminal is an SSH session over vsock, or over a serial port on a Linux kernel without vsock namespaces. Inside the VM, the init runs Claude Code on a pseudo terminal. aibox connects to it, puts your terminal into raw mode and attaches it. When you resize your terminal, the VM gets the new size. When Claude Code exits, aibox exits with the same code.

Tasks

A task is a job Claude Code does on its own while you do something else. It runs in a fresh VM on a clone of the last commit. When it’s done, you get its commits back as a git bundle to review, and the VM is deleted.

%%{init: {"sequence": {"mirrorActors": false}}}%%
sequenceDiagram
  participant You
  participant aibox
  participant VM
  You->>aibox: aibox task "prompt"
  aibox->>aibox: bundle the start commit
  aibox->>VM: prompt and bundle, read-only
  VM->>VM: clone the bundle onto aibox/task
  VM->>VM: Claude Code works and commits
  VM->>aibox: changes.bundle and results
  Note over VM: deleted
  aibox->>aibox: check the bundle
  aibox->>You: fetch command
  You->>You: fetch into a branch and review
aibox task --model sonnet "fix the flaky test in internal/proxy"

What a task gets

The VM gets a clone of HEAD with its full history, not your project folder. Use --from to start from another branch, tag or commit instead.

Note that whatever --from names goes in with its full history. That includes a stash or a commit you reset away. A file you deleted in an earlier commit is still in that history.

Changes you haven’t committed, files git ignores, and branches and stashes that --from doesn’t name stay out of the task. aibox refuses a shallow clone, because it lacks the history.

Claude Code works on the branch aibox/task. It has all permissions, because nobody is there to approve them. Git uses your name and email from your git config. If Claude Code leaves changes uncommitted, aibox commits them in one more commit by aibox <aibox@localhost>. Files git ignores are left out.

The VM starts with an empty home folder and an empty disk, and both are deleted at the end. Your skills, agents and commands in ~/.claude are mounted read-only, like with aibox run, unless the config says none for them. Your CLAUDE.md and your settings don’t come along. Other tasks and aibox run never see the home or the disk, so you can run several tasks at once.

The project config applies like it does for aibox run: allow, mounts, skills, agents, commands, path, env, git, memory, cpus and disk. The task gets its own proxy.log. It doesn’t write to the project’s.

Before you run a task

A task can use everything the config gives it, and nobody stops it. It can reach every host on the allow list and every port of your machine the list names. It can use every secret in env. With GITHUB_TOKEN and preset:github, for example, it can push to your repositories.

Claude Code is told to push or open a pull request only when your prompt asks for it, only to a repository your prompt names by URL, and only to a new branch. Nothing enforces that. A pushed branch or pull request can start the repository’s CI, with its secrets.

Text Claude Code reads, in the project or on the internet, can steer it. When it says what it published, that’s only its word. aibox lists the hosts the proxy connected to, but not what went through those connections. A push to github.com and a fetch from it look the same.

So before you run a task, remove the hosts, ports and secrets it doesn’t need from the config. To let a task fetch and push without a token in the VM, and only to branches you name, use git in the config.

Flags

FlagWhat it does
--fromThe branch, tag or commit to start from. The default is HEAD.
--file, -fAdds the text of a file to the prompt, after the arguments. - reads stdin.
--modelThe model Claude Code uses. Without it, Claude Code uses its default.
--max-turnsThe most turns Claude Code takes.
--max-budget-usdThe most Claude Code may spend by its own estimate, in US dollars at API prices. See Cost.
--timeoutHow long Claude Code may work before it’s stopped. 1h by default, up to 30 days.

--memory, --cpus, --image and --no-sandbox work like they do for aibox run.

Prompt from a file

You can use a plan you wrote as the prompt. --file adds the text of a file after the arguments, and --file - reads stdin. Without any arguments, aibox reads the prompt from stdin, unless stdin is a terminal.

aibox task --file plan.md
aibox task --file plan.md "do only step 2"
cat plan.md | aibox task

A few rules apply:

  • A relative path, and every link in it, has to stay inside the folder you run aibox in. That way a link in a repository can’t make a task read some other file of yours. For a plan somewhere else, give an absolute path. An absolute path must not end in a link.
  • The file has to be a plain file. For a pipe, use --file -.
  • The prompt can be up to 1 MiB of UTF-8 text, the arguments and the file together. A file in UTF-8 or UTF-16 with a byte order mark, as Notepad or > in Windows PowerShell write them, is fine too. The 1 MiB counts the file as it is saved, so a file in UTF-16 holds about half as much.

A plan you haven’t committed only reaches the task as its prompt. It’s not a file in /project.

Login

A task starts with an empty home folder, so Claude Code is not logged in. You give it a login through an environment variable instead. Add one of these to env in the project config:

  • ANTHROPIC_API_KEY for an API key from the Claude Console. Usage is billed to the key.
  • CLAUDE_CODE_OAUTH_TOKEN for your claude.ai subscription. Run claude setup-token to make one.
env:
  - CLAUDE_CODE_OAUTH_TOKEN

aibox never stores the token or the key. It takes the value from your shell when the task starts.

API key

Your API key never enters the VM. Claude Code gets a placeholder key and an ANTHROPIC_BASE_URL that points to a local port. aibox listens there, swaps in the real key and forwards the request to api.anthropic.com. So nothing in the VM can read the key, send it somewhere or put it into a commit.

But while the task runs, it can still spend the key through aibox, on any request to the Claude API. Give the key a spend limit in the Console.

This only works for programs that use ANTHROPIC_BASE_URL. Other programs in the VM only get the placeholder. aibox refuses a config that sets ANTHROPIC_BASE_URL itself next to the key.

An interactive Claude Code still connects to api.anthropic.com and platform.claude.com directly when it starts, without the key. Keep both on the allow list, like a new config has them.

Subscription token

A subscription token goes into the VM as it is, because Anthropic’s terms don’t allow anyone but you to handle it. In the VM, Claude Code and every program the task runs can read it. If something steers the task, it can send the token to a host on the allow list, or write it into a commit or the transcript. Use a separate token or key just for tasks, so you can revoke it on its own.

Whether running unattended tasks is fine on your subscription is your own risk. See Anthropic’s Legal and compliance page. For lots of tasks, or tasks for a team, use an API key.

How the key forwarding works

The port is 127.0.0.1:3129. The proxy answers there itself and doesn’t connect anywhere for it. It forwards each request under /v1/, and the check Claude Code makes at /api/hello, to https://api.anthropic.com, no matter which host the request names. It refuses paths with dot segments or escapes. It drops any credentials the VM sent and adds the key. proxy.log shows these requests as localhost:3129.

aibox reads the root certificates before it locks itself down. After that, its sandbox stops it from reading them, and on macOS from asking the system to check a certificate.

Results

When the task ends, aibox prints the hosts the VM connected to, how the task went, Claude Code’s last message and the command that fetches the changes into a branch:

17:09:25 aibox[259e07]: the VM connected to api.anthropic.com:443
17:09:25 aibox[259e07]: Claude Code exited with 0
17:09:25 aibox[259e07]: 4 turns in 8.321s, about 0.05 USD at API prices, ended by completed
17:09:25 vm[259e07]: | I added a "Purpose" section to the README and committed it as f3572af.
17:09:25 aibox[259e07]: the changes end at f3572af8c6df, fetch them with
17:09:25 aibox[259e07]:   git -c transfer.fsckObjects=true fetch .../changes.bundle aibox/task:aibox/task-20261007-170916-259e07

Each line starts with the time, where the line comes from and the end of the task’s ID. That way you can tell tasks apart when they share a log. vm lines come from the VM and are only what the task claims. aibox lines come from aibox on your machine.

On Windows the fetch command names the bundle with forward slashes, and quotes it only as PowerShell, cmd.exe and Git Bash all read it, so you can paste it into any of them. A path with $, `, % or ! in it has no quotes all three read, so then the command is quoted for PowerShell.

Reviewing the commits

Treat the commits like a pull request from a stranger until you’ve read them. The authors on them prove nothing, because the task can set any author.

  1. Fetch them with the command aibox prints. aibox only prints it after checking that the bundle has aibox/task and nothing else, and needs no commit other than the one the task started from. The fetch only adds that one branch.
  2. Read the whole change with git diff <commit> aibox/task-…, no matter what the single commits look like. Take <commit> from the line aibox[…]: task … starts from … that aibox prints. Don’t take it from result.json, because the VM writes that file. The printed commit is the one aibox checked the bundle against.

Checking out, merging or rebasing the branch puts its files into your project folder. From then on, everything in What the VM writes applies.

Cost

The cost aibox prints is Claude Code’s own estimate, from the tokens it used at the API’s list prices. With an API key it’s close to what you’re billed. With a subscription you aren’t billed this amount. The task counts against your plan’s limits instead.

--max-budget-usd caps the same estimate, for Claude Code alone. Programs the task starts, like another claude, don’t count. The only limit on time or spending that aibox enforces itself is --timeout. With an API key, give the key a spend limit in the Console.

The task folder

Each task has a folder tasks/<start time>-<id> in the project’s folder in ~/.aibox/projects. aibox prints the path on stdout, for scripts.

FileWhat’s in it
changes.bundleThe commits. Empty when the task made none.
result.jsonWhat the VM reports: the commit the task started from and the one it ended at, Claude Code’s exit code, whether it ran out of time, whether aibox committed leftovers, the files that were cut, warnings, the error of a failed step, and Claude Code’s last line with the cost and the turns.
transcript.jsonlEverything Claude Code did, as stream-json, cut at 256 MiB.
claude.logWhat Claude Code wrote to standard error, cut at 1 MiB.
console.logThe kernel and init messages of the VM.
proxy.logEvery host and port the proxy connected to or refused for the task, once, with the time it first happened. When the task ends, a summary says how often and when last. A killed aibox writes no summary. aibox writes this file, not the VM. It shows the name the VM asked for, not what went through.
share/The prompt, the settings and the bundle of the start commit with its full history, until aibox tasks clean removes it.

Next to the folder, tasks/<start time>-<id>.lock is locked while the task runs, so aibox tasks clean leaves the task alone. aibox tasks clean --all removes it with the folder, so use that rather than removing a folder by hand.

aibox exits with 1 when the task didn’t finish. That happens when Claude Code failed or ran out of time, a step in the VM failed, the VM was stopped, or the VM sent results aibox doesn’t accept. The folder then has whatever the task left behind.

Removing old tasks

Every task keeps its folder, and with it a bundle of the project’s history. aibox never removes anything on its own. To clean up, run:

aibox tasks clean

This removes the bundles of the tasks that ended and keeps their results. Running tasks are left alone.

aibox tasks clean --all --older-than 720h

--all removes the whole folders of ended tasks, results included. --older-than only cleans tasks that started longer ago than that. The command also removes what’s left of tasks that failed while starting. If it can’t check or clean a folder, it names it and exits with 1.

A task counts as ended once its aibox process ends. The VM ends with aibox, even when aibox is killed.

How a task runs

aibox task boots the same VM as aibox run, but without a terminal. The kernel command line tells the init to run a task.

First, aibox writes the prompt, the settings and a bundle of the start commit into the task’s folder. The VM gets that folder as a read-only share. git can only bundle refs, so aibox makes the bundle from an empty repository that borrows the project’s objects and has the commit as its HEAD. Your project gets no new ref.

Your project folder and the project’s home folder are not shared with the task. The mounts from the config and your skills, agents and commands are, read-only like with aibox run.

Each task gets a new state disk, which the init formats. The home folder and the project are folders on that disk. aibox deletes the disk’s file as soon as the VM has opened it, so the disk is gone when the VM is. Tasks share a lock on the project, so they can run next to each other and next to aibox run, but aibox projects remove leaves the project alone while one runs.

Then the init clones the bundle, removes the remote and switches to the branch aibox/task. It runs claude --print as the user, with bypassPermissions, stream-json output and image/task.md as an extra system prompt, followed by the part of image/host for the system your machine runs. On Windows a task gets a part of its own, which warns about the names a checkout on Windows can’t hold. Finally it commits whatever is left and bundles the branch.

aibox runs its git commands with hooks and fsmonitor turned off, because the repository came from the task. Filters the task sets up still run, but only inside the VM.

Each step runs in its own cgroup. When the step ends, the init kills the cgroup, so nothing a step started outlives it. The git steps before Claude Code get 10 minutes together, and the ones after get another 10. Claude Code gets the task’s timeout. aibox stops the VM at the latest 25 minutes after the timeout.

The results come back over the SSH session, as a tar on its standard output. The init sends each file once it’s complete, and result.json last. Progress goes over standard error.

aibox checks what comes back:

  • It only accepts files it knows by name. Each one has to be a plain file, sent once, and within a size limit. If aibox refuses the results, it stops the VM.
  • It reads the bundle’s header without git. It checks that the bundle has aibox/task and nothing else, and needs no commit other than the start commit.
  • It strips control characters from text the VM sends before it reaches your terminal. It adds the time, the source and the end of the ID to each line from the VM itself, so a line from the VM can’t pretend to be from aibox. aibox run passes the VM’s terminal straight through, so this only applies to tasks.

aibox opens the result files before the VM starts. Once the VM runs, aibox locks itself down and can’t open files anymore. For the same reason a task can’t remove its input bundle when it ends, so aibox tasks clean does that later. Each task locks a file next to its folder while it runs, and makes the folder only once it holds the lock. That’s how aibox tasks clean knows to leave a running task alone. The lock is next to the folder, not in it, because Windows can’t remove a folder that holds an open file. aibox 1.6 and before kept the lock in the folder, and doesn’t see one next to it, so don’t run aibox tasks clean of an older aibox while a task of a newer one runs. A lock whose folder never got made, by a task that failed to start, goes with the next aibox tasks clean a minute later.

Project config

Each project has its own config at ~/.aibox/projects/<escaped path>/config.yaml. It lives outside the project on purpose, so the VM can’t change it. The first aibox run writes it, with comments and the hosts Claude Code needs, or from your own starting config.

To change it, run:

aibox config edit

This opens the config in the same editor git would use: $VISUAL, then $EDITOR, then vi, or Notepad on Windows. Pass --editor to pick another one. When you close the editor, aibox checks the file and tells you about mistakes.

An editor that returns right away, like code without --wait, gets checked before you’ve made your changes.

Your own starting config

If you use the same hosts, mounts or memory in most projects, you don’t have to add them to each one. Write them into a template once:

aibox config edit --template

This opens ~/.aibox/config.yaml, which starts as a copy of the default config. From then on, every new project gets a copy of it, comments included, instead of the default. Projects that have a config already keep it, and a change to the template doesn’t reach them. So each project’s config still says everything its VM gets.

If the template has a mistake, aibox says so and starts no new project from it.

Here is a full example:

allow:
  - api.anthropic.com
  - claude.ai
  - preset:go
  - preset:github
  - registry.example.com:5000
  - "*.example.org"
memory: 4096
cpus: 4
disk: 32
mounts:
  - ~/sdk/go1.26.8:/opt/go
skills: all
settings: none
ide: true
clipboard: false
path:
  - /opt/go/bin
env:
  - GOFLAGS=-mod=mod
  - GITHUB_TOKEN
git:
  - remote: github.com/owner/repo
    fetch: true
    push:
      - aibox/*

git has its own page, Git.

allow

The hosts the VM can connect to. The proxy refuses everything else.

  • example.com allows port 443. Write example.com:8443 for another port.
  • *.example.com matches every subdomain of example.com, but not example.com itself.
  • preset:NAME stands for the hosts a tool needs. See the table below.
  • 127.0.0.1:8123 or [::1]:8123 is a port on your machine. See Ports on your machine.

aibox refuses a name that resolves to an address in your machine’s own networks, like loopback, link-local or private addresses. The only way to allow such an address is to write it into the list.

A new config only allows the hosts Claude Code needs:

HostWhy
api.anthropic.comthe Claude API
claude.ailogging in with a claude.ai account
claude.comthe sign-in page redirects through here
platform.claude.comlogin tokens
mcp-proxy.anthropic.comMCP connectors of a claude.ai account
code.claude.comlooking up documentation

These are the presets:

PresetHosts
preset:goproxy.golang.org, sum.golang.org, storage.googleapis.com, vuln.go.dev, dl.google.com, go.dev
preset:npmregistry.npmjs.org, registry.yarnpkg.com, nodejs.org
preset:pypipypi.org, files.pythonhosted.org
preset:cargocrates.io, static.crates.io, index.crates.io, static.rust-lang.org
preset:githubgithub.com, api.github.com, codeload.github.com, *.githubusercontent.com
preset:dockerregistry-1.docker.io, auth.docker.io, index.docker.io, production.cloudfront.docker.com

When the proxy refuses a host that a preset allows, its message names the preset.

Suggesting presets

A new config doesn’t know which tools your project uses. To find out, run this in the project folder:

aibox config suggest

aibox looks at the names of the files in the folder and suggests the tools they point to, with the files that show each one and the hosts of its preset. It asks for each tool before it adds its preset to allow. Start aibox again for them to apply. Without a terminal to ask, or when allow isn’t a list with one entry per line, it only prints the lines to add with aibox config edit.

PresetFiles
preset:gogo.mod, go.work
preset:npmpackage.json, package-lock.json, yarn.lock, pnpm-lock.yaml
preset:pypipyproject.toml, requirements.txt, setup.py, setup.cfg, Pipfile, uv.lock, poetry.lock
preset:cargoCargo.toml, Cargo.lock

A project may come from a stranger, so it must not pick its own permissions. That’s why aibox only reads the names of plain files, and only in the top folder, and never what’s in them. It only ever suggests these presets, never a host, a port, a mount or a variable from the project. preset:github and preset:docker are never suggested. A git remote or a Dockerfile doesn’t mean the VM needs them.

Note that anyone can upload files to package registries, so a program in the VM can send data there too. Only add a preset the project needs.

Ports on your machine

Some servers only listen on your machine’s loopback, like a local MCP server. Once you allow the port, the VM reaches it at the same address:

allow:
  - 127.0.0.1:8123
claude mcp add -t http my-server http://127.0.0.1:8123/mcp

Write the address the server listens on, 127.0.0.1 or [::1]. The VM only has IPv4, so in the VM the port is at 127.0.0.1 and localhost either way. That also means you can allow a port on only one of the two addresses.

You can’t write localhost. It means both addresses, and any user on your machine could listen on the one the server doesn’t use. 0.0.0.0 and :: aren’t allowed either, because they lead to the loopback too.

This works for any protocol over TCP, also for programs that don’t use the proxy. A program in the VM can’t listen on such a port itself. Port 3128 can’t be allowed, because the VM’s proxy is there. With ide turned on, 3131 can’t be allowed either, because Claude Code reaches your IDE there.

Keep in mind that the server runs as you, on your machine, outside the VM, and does what the VM asks it to. An IDE’s MCP server, for example, often has no login and can run commands and change files outside the project. Allowing its port lets the VM out. Only allow a port for a server you’d trust with whatever the VM asks of it. For your IDE, turn on ide instead of allowing its port.

How it works

aibox passes the ports to the VM’s init on the kernel command line. The init listens on 127.0.0.1 in the VM for each port. It sends each connection to the proxy as localhost and the port. For localhost the proxy doesn’t ask DNS. It connects to the one loopback address the allow list names for that port. That’s why programs that ignore the proxy variables reach the port too. It also means the server sees the Host it expects. Many local servers refuse other names, to guard against DNS rebinding.

memory, cpus

The VM’s memory in MiB and its number of CPUs. Without them, the VM gets 2048 MiB and 2 CPUs. The flags --memory and --cpus override both.

disk

The size in GiB of the disk that keeps the VM’s /usr/local and ~/.cache between runs. The default is 16. The disk only takes up space on your machine as it fills.

The size is fixed when the first run creates the disk. Changing it later has no effect, until you start the VM over with aibox projects reset, which makes a new disk.

mounts

Folders on your machine that the VM can read, but not write. Write them as host:guest. guest is the path in the VM. A host path starting with ~/ starts in your home folder.

  • The guest path has to stay clear of the folders the VM needs: /project, /dev, /proc, /sys, /run, /tmp, /etc, /bin, /sbin, /lib, /lib64, /usr and /root. It also can’t overlap with other mounts.
  • The host folder can’t be your home folder or a folder above it, because the VM would see your keys and shell files.
  • The host folder can’t be ~/.aibox, a folder in it or a folder above it, because the VM would see every project’s config and Claude Code login.
  • aibox compares folders rather than paths for these two rules, so a symlink or another spelling doesn’t get around them.
  • A mount inside /home/user is fine. The VM’s home is a folder aibox manages, not your home folder. Leave /home/user/.claude itself alone, because the VM’s login lives there.
  • The VM caches names and attributes for the whole run. If you change the folder on your machine, a running VM may not see it.
  • On macOS the VM runs arm64 Linux, so Mac programs don’t run in it.
  • On Windows the VM runs amd64 Linux, so Windows programs don’t run in it, and files made on Windows aren’t executable there. Mount data, not tools, and install tools in the VM into /usr/local.
  • The host folder can’t be the root of a drive or share either.
  • On Windows write the host folder with its drive, like C:\Users\me\sdk:/opt/go, or with forward slashes, C:/Users/me/sdk:/opt/go. ~\ works like ~/. Don’t put the entry in double quotes, because YAML reads backslashes in double quotes as escapes; single quotes are fine.

skills

If you have skills in ~/.claude/skills, aibox mounts them read-only at the same place in the VM’s home, so Claude Code finds them. That’s the default, skills: all. To keep your skills out of the VM, set:

skills: none

To give the VM only some skills, mount them inside /home/user/.claude/skills. A mount there takes the place of the whole folder:

mounts:
  - ~/.claude/skills/pdf:/home/user/.claude/skills/pdf

A symlink in the skills folder that points outside it doesn’t work in the VM.

aibox run and tasks get the same skills.

agents, commands, claude-md, settings

The VM gets more of your Claude Code setup, so it works like on your machine:

  • agents and commands: your folders ~/.claude/agents and ~/.claude/commands, read-only, like your skills. Tasks get them too.
  • claude-md: your ~/.claude/CLAUDE.md. aibox run copies it into the VM’s home at each start.
  • settings: a few settings of your ~/.claude/settings.json, like model and theme. aibox run puts them into the VM’s own settings at each start, and keeps the others the VM has.

All four are all by default. To keep one out, set it to none:

claude-md: none
settings: none

A mount inside /home/user/.claude/agents or /home/user/.claude/commands takes the place of that folder, like with skills.

aibox doesn’t remove what it put into the VM’s home before. If you delete your CLAUDE.md or a setting, or turn one of them off, the VM keeps the last copy. aibox projects reset starts the VM’s home over. When the VM left something there that keeps a file from going in, aibox says so and starts the VM anyway.

Only these settings come along: model, theme, attribution, includeCoAuthoredBy, outputStyle and alwaysThinkingEnabled. Settings that start a program or hold a secret stay on your machine: hooks, env, apiKeyHelper, statusLine and the permissions. So do your plugins and your MCP servers. The VM starts with its own.

A task starts with an empty home folder, so it doesn’t get your CLAUDE.md or your settings.

ide

Claude Code can use the IDE that has your project open, like VS Code or GoLand. It sees the code you select, shows its changes as a diff in the editor and reads the errors the IDE reports. This is off by default, because aibox then passes messages between the VM and a program on your machine. To turn it on, set:

ide: true

aibox only passes on what’s needed, and only for files in the project. Read In your IDE before you turn it on, especially the part about diffs.

With ide turned on, your env can’t set CLAUDE_CODE_SSE_PORT, because aibox sets it itself. Tasks never get the IDE, only aibox run does.

clipboard

When you press Ctrl+V in Claude Code, or Alt+V on Windows, it pastes the image on your clipboard. The VM can read your clipboard only right after you press the key, and only once. This is on by default. To turn it off, set:

clipboard: false

The VM reaches the clipboard on port 3132 of its loopback, so with the clipboard on, allow can’t name that port. Tasks never get the clipboard, only aibox run does. See Pasting images.

path

Folders in the VM to put in front of its PATH, for Claude Code and the shell. For example /opt/go/bin from the mount above.

An entry can also be the name of a variable, like PATH or DIRENV_PATH. It then stands for the folders in that variable on your machine, without the relative and empty ones. That way the VM finds the tools of a nix develop or direnv shell, as long as /nix/store is mounted at the same path. On Windows the folders of such a variable are Windows folders, which the VM doesn’t have, so it adds none of them.

env

Environment variables for the command in the VM. NAME=value sets a value. NAME alone passes on the value your shell has when the VM starts. That’s how you get a secret in without writing it into the file.

aibox refuses the variables it sets itself: AIBOX, HOME, USER, LOGNAME, SHELL, PATH, TERM, LANG, the proxy variables, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and GOMODCACHE.

ANTHROPIC_API_KEY is the exception. Its value is never passed on. With aibox run and with tasks, the key stays on your machine and the VM gets a placeholder. See API key.

Git

Claude Code often needs to fetch from or push to a repository. You could put a token into env, but then everything in the VM can read it and use it for anything the token allows.

The git list in the project config is the safer way. It names the repositories git in the VM can fetch from and push to, using the git login on your machine. The login never enters the VM.

git:
  - remote: github.com/owner/repo
    fetch: true
    push:
      - aibox/*
      - feature/login
  - remote: github.com/owner/private-module
    fetch: true
  • remote is the host and path of the repository, without https:// and without .git.
  • fetch: true allows clone and fetch.
  • push lists the branches a push can create or update. * matches any part of a name without a /. So aibox/* allows aibox/fix but not aibox/a/b, and * on its own allows main too.

aibox refuses pushes to other branches, deletes, tags, and pushes from a shallow clone. If one branch of a push is refused, the whole push is. aibox also refuses repositories the list doesn’t name.

Using it

In the VM, use git like you normally would. git clone, git fetch and git push work with the addresses https://HOST/PATH, git@HOST:PATH and ssh://git@HOST/PATH.

aibox sends these requests to a broker on your machine. The broker adds your login and passes on what the list allows. When it refuses something, git in the VM shows you why. The broker writes what it refused, and every push it passed on, into proxy.log.

%%{init: {"sequence": {"mirrorActors": false}}}%%
sequenceDiagram
  participant git as git in the VM
  participant broker as Broker in aibox
  participant server as Git server
  git->>broker: clone, fetch or push
  broker->>broker: check it against the git list
  alt allowed
    broker->>server: the request, with your login
    server-->>broker: answer
    broker-->>git: answer
  else refused
    broker-->>git: why it was refused
  end

The host doesn’t need to be on the allow list for git.

To go get a private module, set GOPRIVATE in env. For hosts other than GitHub, Go first asks the host over HTTPS where the module is, so that host needs to be on the allow list too.

SSH

Before the VM starts, aibox asks git on your machine for the login of each repository, the same way git push over HTTPS would. If git has none, the broker talks to the server over SSH instead, as git@HOST. aibox tells you when it does that.

aibox asks ssh -G how ssh would reach the host. So these settings from your ~/.ssh/config apply: HostName, Port, IdentityFile, IdentitiesOnly, IdentityAgent, UserKnownHostsFile, GlobalKnownHostsFile and HostKeyAlias.

It offers the same keys ssh would, from your SSH agent and from key files without a passphrase. A key with a passphrase works through the agent, which has to keep running while aibox does. aibox refuses a host that your config reaches through ProxyJump or ProxyCommand.

On Windows aibox uses the agent of Windows’ OpenSSH, the service “OpenSSH Authentication Agent”, unless SSH_AUTH_SOCK names another one. Windows turns that service off by default: start it with Start-Service ssh-agent, set it to start by itself, and add your key with ssh-add. The agent of Git Bash sets SSH_AUTH_SOCK to a path only Git Bash can reach, so unset it for aibox. It reads the known hosts of everyone from %ProgramData%\ssh\ssh_known_hosts, as that ssh does. It asks the ssh that comes first on your PATH. The one of Git Bash writes its paths as /c/Users/..., which aibox can’t read, so keep Windows’ own ssh first.

Your known hosts check the server. aibox refuses a server that isn’t in them, so connect to it with ssh once to add it.

The broker logs in once and runs git’s requests from the VM over that connection.

If neither HTTPS nor SSH works, aibox stops. It reads the logins and keys only once, so restart aibox when one expires.

For SSH, aibox keeps a connection to your SSH agent open while it runs, and can connect to the SSH server’s port. To limit what a bug in aibox could do with that, give aibox its own key, or use an agent that asks before it signs, like with ssh-add -c.

Your name and line endings

The VM has a home of its own, without your git config. So aibox gives git in the VM the name and email git uses in your project. aibox run also gives it your core.autocrlf. Git for Windows sets that to true when it installs, and checks out files with CRLF line endings. Without the setting, git in the VM would see every file as changed, and a commit would change the line endings of every file. A task doesn’t need it, since the VM clones the repository itself.

Settings of your own in env win, such as GIT_AUTHOR_NAME, or core.autocrlf in a GIT_CONFIG_KEY_*.

What the broker can’t stop

The broker can’t stop a force push to a branch you allowed. It sees which branches a push updates, but not whether the push drops commits. Protect the branches that matter on the server, for example with branch protection on GitHub.

What the VM pushes is code you haven’t read. A pushed branch can start the repository’s CI, with changes to its workflows and with its secrets.

With push but no fetch, the VM still sees the names of all branches and tags and the commits they point to, because git needs them to push. It doesn’t get the files.

With git, aibox sets GIT_CONFIG_COUNT, GIT_CONFIG_KEY_* and GIT_CONFIG_VALUE_* in the VM, so you can’t set them in env.

How the broker works

aibox asks git on your machine for each repository’s login before the VM starts. It runs git outside any repository and without a terminal. That way the project’s git config, which the VM can write, has no say.

git in the VM gets a config that sends the HTTPS and SSH addresses of each repository to http://127.0.0.1:3130. The init forwards that port to the proxy like the ports on the allow list, and the proxy serves the broker there.

The broker speaks git’s smart HTTP protocol. It only accepts git’s requests, only for the repositories on the list, and only without dot segments or escapes in the path. It sends each request to https://HOST/PATH.git, where the address comes from the config and not from the request. It adds the host’s login and drops the VM’s credentials and cookies. When the server refuses the login or redirects, the VM gets an error. That way git in the VM never asks for a login of its own. The broker also refuses a fetch the config doesn’t allow.

For a push, the broker reads the commands at the start. They name each ref with its old and new commit. The broker only passes the push on when every command updates or creates a branch on the list. Otherwise it refuses all of them, because the pack belongs to the whole push. It also refuses deletes, tags, signed pushes, push options and pushes from a shallow clone. It answers in git’s format, so git in the VM shows why. A push larger than git’s buffer starts with a probe that is just a flush, which the broker passes on.

Over SSH

Before the VM starts, aibox asks ssh -G for the host name, port, key files, agent and known hosts ssh would use. It reads the known hosts and the keys. The server’s port gets added to the ports aibox may connect to.

The broker only asks the server for the key types your known hosts have for it. It logs in once per repository and opens a session on that connection for each request. If the connection breaks, it logs in again. If the server doesn’t open a session in time, the broker logs in again too. It closes the old connection once the requests still on it are done. If the server refuses a session, for example because it has too many open, only that request fails. The other sessions on the connection go on.

HTTP is stateless and SSH isn’t. So for each request from git in the VM, the broker runs the service on the server once:

  • For the advertisement, it passes on what the server advertises, with the line that names the service over HTTP, and then ends the service.
  • For a request, it skips the advertisement, sends the body and passes on the answer. If git compressed the body for HTTP, the broker decompresses it first.

git in the VM sends Git-Protocol, which the broker passes on as GIT_PROTOCOL, so a fetch runs in protocol version 2. The checks of a push are the same as over HTTPS.

Logging in, opening and closing a session, the advertisement and every pause in an answer have a time limit. When git in the VM gives up on a request, the broker ends its session. What the server writes to standard error goes into the error and the log.

A path on the server that starts with - or ~ isn’t allowed in the config, because the server would read it as an option or a home folder.

Containers and VMs

You can run containers and VMs inside the VM, without root. Bring podman or QEMU from your machine like any other tool, with mounts and path. On Windows your machine has only Windows programs, which the VM can’t run, so install them in the VM into /usr/local, which stays.

A container shares the VM’s network. It only has the loopback and the proxy, and gets the proxy variables. To pull images from Docker Hub, add preset:docker to the allow list.

podman keeps its storage on the state disk, in ~/.local/share/containers. overlayfs doesn’t work on the shared folders, so it can’t live in the shared home.

podman’s limits, like --memory and --pids-limit, work. Setuid programs in an image don’t, because the state disk is mounted with nosuid.

VMs

The VM has /dev/kvm for VMs of its own. On Linux this needs nested virtualization on your machine. On macOS it needs a Mac where Virtualization.framework offers nested virtualization. Apple documents that for M3 and newer, with macOS 15 or newer. On Windows the VM never has /dev/kvm: Hyper-V gives the VM of aibox no virtualization of its own. Everywhere else, those VMs run in software.

How it works

The VM’s kernel has user namespaces, cgroups, overlayfs and KVM. The init opens /dev/kvm and /dev/fuse to everyone and mounts cgroup2. The image has newuidmap and newgidmap, with a range of subordinate IDs for the user. The init gives the user a cgroup with the cpuset, cpu, io, memory and pids controllers, and starts the command in a cgroup below it.

What the VM writes

With aibox run, the VM can write to your project folder. That’s on purpose, it’s how you see Claude Code’s changes right away. But it also means that anything in the project folder can come from the VM. Treat it like a pull request from a stranger until you’ve read it.

This matters most for files that tools on your machine run without asking:

  • lefthook runs the commands in lefthook.yml
  • direnv runs .envrc
  • just reads the justfile
  • npm runs the scripts in package.json
  • your IDE starts the run configurations in .idea/

All of them run straight from the working tree.

.git

The same goes for .git. A setting in .git/config, like core.fsmonitor or core.hooksPath, or a hook in .git/hooks, runs the next time you use git on your machine. Neither git status nor git diff shows it.

aibox does not protect these files. Mounting them read-only in the VM doesn’t help. git and IDEs replace .git/config every time they write it, for example on git push -u, and the mount stays with the old file.

What to do

  • Only trust a project in your IDE when it’s your own. GoLand, for example, asks about this when you first open a project.
  • Read the diff of what the VM changed before you run a build, a test, a script or a run configuration of the project on your machine. Do the same before you commit with hooks the project defines.
  • Check .git/config and .git/hooks for changes you didn’t make.

Security

A prompt or a file in a repository can steer Claude Code. So aibox doesn’t trust anything in the VM: not Claude Code, not the programs it runs, and not what they write.

Threat model says who aibox defends you against and where the trust boundaries are. This page has the details.

To report a vulnerability, see SECURITY.md.

What aibox protects

  • The VM can’t see your other files. With aibox run it sees your project folder. A task only sees a clone of a commit. Both also see the folders you list in mounts, and your skills, agents and commands from ~/.claude, read-only. aibox run also copies your ~/.claude/CLAUDE.md and a few settings that start no program and hold no secret into the VM’s home. The project config can keep each of them out. See agents, commands, claude-md, settings. aibox won’t start in your home folder or above it.
  • The VM can only reach the hosts on the allow list. It has no network card, only a proxy on your machine. aibox refuses a name that resolves to your machine’s own networks, unless the list names that address itself. A port on your machine’s loopback is only reachable when the list names it. See allow.
  • Your API key stays on your machine. The VM gets a placeholder, and aibox adds the real key to the requests that go to the Claude API. See API key.
  • Your git login stays on your machine too. A broker adds it to git’s requests from the VM, but only for the repositories and branches the config names. See Git.
  • The VM can’t read your clipboard whenever it likes. It gets one image, and only right after you press Ctrl+V, or Alt+V on Windows. See Pasting images.
  • The programs that run the VM are locked down. On Linux QEMU runs in a bubblewrap sandbox, and aibox restricts itself with Landlock and seccomp. On macOS aibox restricts itself with Seatbelt. See The host side.
  • aibox only takes a downloaded image if its SHA-256 was built into its release. Before each start a release checks the kernel against the SHA-256 built into it, whether it downloaded the image or a package installed it. An image you pass with --image isn’t checked, and neither is the image of a build from a checkout. On Linux QEMU boots the copy aibox checked. On macOS Virtualization.framework reads the kernel again from its file, so a change right after the check isn’t caught. See Packaging aibox.

What aibox does not protect

  • What the VM writes into the project. That includes .git, and files that tools on your machine run without asking. Read What the VM writes.
  • What you allow. The VM can reach every host on the allow list and every port of your machine the list names. It can use every secret in env and push to every branch the git list names. A subscription token goes into the VM as it is, so the VM can read it. A task can use all of this, with nobody there to stop it. See Tasks.
  • What goes through an allowed connection. proxy.log shows the host and port the VM asked for. It doesn’t show the address behind the host, or what was sent. A push to github.com and a fetch from it look the same.
  • What a task says about itself. Claude Code’s last message, result.json and the authors of the commits all come from the VM.
  • Force pushes and CI. The git broker can’t tell a force push to an allowed branch from a normal push. A pushed branch can start the repository’s CI, with its secrets. See What the broker can’t stop.
  • Spending. The only limit on time or spending that aibox enforces itself is a task’s --timeout. Give an API key a spend limit in the Console.
  • Diffs you accept or edit in your IDE. aibox checks that a diff is for a file in the project, but the IDE opens the file later. In between, the VM can replace the file with a symlink to a file outside the project. If you then accept or edit the diff in the IDE, the IDE writes through the symlink. Approve changes in the terminal instead. See In your IDE.
  • Your clipboard right after Ctrl+V, or Alt+V on Windows. aibox can’t tell which program in the VM you pressed the key in. After a Ctrl+V in bash or vim, the VM can read the image on your clipboard too. aibox also counts on your terminal not to echo text a program chose back as input, as current terminals don’t. A terminal that does lets any program type into it, with or without aibox.
  • Gatekeeper on macOS. The VM can set and remove extended attributes on files in the project, including com.apple.quarantine, and files it creates have none. So Gatekeeper never checks a program that comes out of the VM. Virtualization.framework has no option to prevent that. Like everything the VM writes, such a program is untrusted until you’ve read it.

How it works

The VM

On Linux the VM runs on QEMU’s microvm machine type. On macOS it runs on Virtualization.framework.

The project folder of aibox run, the mounts, the skills, the agents and the commands are shared into the VM with virtio-fs, or with 9p on Windows. Your user on your machine is the user in the VM, so the files it creates belong to you. Your machine enforces the read-only shares, not the VM.

The root disk is read-only, for QEMU and for the VM. The init puts an overlay in RAM over it, only to create the mount points, and then makes the root read-only again. In the VM, only /tmp, /run, /dev/shm, the home, the project and the state disk can be written to.

The kernel has no modules, no network drivers, no bpf() syscall, no ftrace and no kprobes. The kernel QEMU boots on amd64 has no PCI and no ACPI either. The arm64 kernel has them. The amd64 image also carries vmlinuz-hyperv, a kernel for Hyper-V on Windows, which needs both; Linux never boots it. On Windows the shares are 9p, served by aibox itself, which enforces the read-only ones as virtio-fs does elsewhere.

The network

All of the VM’s traffic goes over vsock to the proxy on your machine. The terminal is an SSH session over the same vsock. On a Linux kernel without vsock namespaces, a serial port takes the place of vsock, see below.

On Linux each VM gets its own vsock namespace. aibox creates it in an unprivileged user namespace, so it doesn’t need root. Every VM uses the same address, CID 3, and no other process on your machine can connect to it.

Linux has vsock namespaces since 7.0. On an older kernel, or one without vsock, the VM has no vsock at all. It reaches aibox over a virtio serial port instead. QEMU connects the port to a socket pair, and aibox holds the other end. No file on your machine leads to it, so no other process can reach the VM. The proxy, the terminal and everything the proxy carries share the port, as streams that yamux multiplexes on it.

On macOS aibox owns the VM, and only the process that owns a VM can reach its vsock. So it doesn’t need a namespace.

On Windows the VM’s sockets are Hyper-V sockets, and the service that makes the VM lets only your user and the helper that serves its proxy listen on them. Connecting into the VM stays with administrators, and nothing does it. The helper runs in an AppContainer of its own, and the service puts filters of the Windows Filtering Platform on that container: it connects only to the TCP ports of the allow list, to any host on them, and, on your machine’s loopback, to the ports of the allow list and the one aibox serves the services of the proxy on. Windows keeps an AppContainer off the loopback, so the service lets this one on, in a list of the whole machine that it records, and takes it off again once aibox has ended the helper, or at its next start after a crash, or when it is uninstalled. The filters don’t cover names: Windows looks them up for the helper outside its container, so a VM that took the helper over could look up any name, and send data out in the names it asks for. The helper holds no secret of aibox’s to send.

The Windows service

Hyper-V serves only administrators, so on Windows a service that runs as SYSTEM makes the VMs. It is the one part of aibox with more rights than you, and it keeps them to itself: it boots its own copy of the kernel and the root disk, from a folder only administrators can change, and takes no path from you. How it works says what it takes instead. The VM gets at one file of yours, the state disk, only after the service checked that you may write it, and only until the VM is gone: the entry on the file is for an account of that one VM, which goes with it. A VM may have as much memory and as many processors as the machine has, and all VMs together no more memory than that. What the service refuses, it says; what fails inside it goes to the Application event log, not to you.

The host side

On Linux QEMU runs in a bubblewrap sandbox with no network and no writable file system. Its only environment variable is LD_LIBRARY_PATH, which names the folders its libraries are in. It only sees its own program, libraries and firmware and a copy of the kernel. Before QEMU starts, aibox asks your system’s loader which libraries QEMU loads and binds only those files. Devices, disks and sockets come in as open files from aibox. QEMU also filters its own syscalls. aibox run --no-sandbox runs it without bubblewrap, for debugging.

Once QEMU runs, aibox locks itself down too. It gives up the right to gain new privileges. Landlock rules only let it read /etc for DNS and connect to the ports on the allow list. A seccomp filter blocks ptrace, mount, namespace and similar syscalls. This needs Linux 6.7 or newer.

On macOS aibox locks itself down with Seatbelt, the macOS sandbox, once the VM runs. From then on it reads the contents of no files except the resolver’s, though it can still see the metadata of all files. It only connects over TCP to the ports on the allow list, and to the system’s name service. It can’t start programs or read or signal other processes. Files it opened before, like the logs, and the VM keep working.

Before the VM starts, aibox works in folders an earlier VM could write. git reads the project’s .git for your identity and for the bundle of a task. With ide: true aibox writes the IDE’s lock file into the VM’s home. A symlink the VM left there could lead a write to a file outside. So aibox does that work in a helper, a short-lived copy of itself that the kernel lets create, change and delete files in one folder only. On Linux that’s Landlock, on macOS Seatbelt. The kernel checks where a path really leads, so a symlink out of the folder can’t be written through, by the helper or by a program it starts. When git reads .git it may write no file. For the bundle of a task it may write only in the task’s folder in ~/.aibox. The lock file goes into the VM’s home. The helper can still read any file you can and reach the network. On Linux it can also still change the permissions, owner, times and extended attributes of a file outside, since Landlock doesn’t restrict those.

On Windows aibox serves the VM from two helpers, copies of itself. One serves the shares, with your rights, as it must to read and write them, and does nothing else. The other serves the proxy, in an AppContainer: it parses what the VM sends through the proxy, reads none of your files, holds no secret, has no console and reaches only the network the service lets it. The Claude API forwarder, the git broker, the IDE bridge and the clipboard stay in aibox, with the API key, your SSH keys and git logins and the token of the IDE: the helper hands their requests to aibox over a port of the loopback, which answers only requests with a token the helper got from aibox. The terminal stays in aibox as well. aibox reads what the helper says as it would what the VM says. Both helpers are in a job, which Windows ends with aibox, however aibox ends. The AppContainer has a profile under your account, which aibox removes when the helper ends. A profile an aibox that was killed left behind goes with the next run without --no-sandbox. aibox run --no-sandbox runs the helper of the proxy outside its container, with no filters.

With the clipboard on, aibox starts a helper before it locks itself down. The helper isn’t locked down, because it has to start osascript, wl-paste or xclip. It only reads the clipboard when aibox asks, and ends when aibox does.

On macOS the VM runs in a Virtualization.framework process. aibox can’t put that process in a sandbox of its own, like it does with QEMU and bubblewrap. That’s why --no-sandbox changes nothing on macOS.

The VM ends with aibox, even when aibox is killed or crashes. On macOS the Virtualization.framework process does, and it also serves the shares, so nothing is left behind. On Linux aibox starts QEMU and virtiofsd with a parent death signal, so the kernel kills them when aibox dies. bubblewrap does the same for QEMU. The kernel sends that signal when the thread that started the program ends, so aibox starts all of them from one thread that lives as long as aibox. virtiofsd forks the process that serves the share, and that process ends by a signal of its own.

aibox won’t start in your home folder, above it, or above ~/.aibox. It compares folders rather than paths, so a symlink, a different spelling on a file system that ignores case, or a bind mount doesn’t get around it. On a Mac, /users/YOU and /System/Volumes/Data/Users/you are your home folder too, and /System/Volumes/Data contains it. aibox refuses those as well.

aibox checks the host folder of a mount the same way. It also refuses a mount inside ~/.aibox, because each project’s config and login live there.

Threat model

This page says who aibox defends you against, what it keeps safe, and where its limits are. Security has the details of each defense.

Who aibox defends you against

aibox defends you against anything that can steer Claude Code: a prompt, a file in a repository, a web page Claude Code reads or a package it installs. aibox assumes the worst. Whatever runs in the VM is under an attacker’s control, root in the VM included, and it may try bugs in the VM’s kernel.

Other programs on your machine can’t reach the VM either. On Linux each VM has its own vsock namespace, so no other process can connect to it. On a Linux kernel without vsock namespaces, the VM talks to aibox over a serial port that only aibox holds. On macOS only aibox can reach its VM’s vsock.

Other programs also can’t pose as a server you allowed. A server on your machine’s loopback usually listens on only one address, 127.0.0.1 or [::1]. That’s why the allow list takes the exact address and not localhost, which means both. Otherwise another user could listen on the other address and get the VM’s connections.

What aibox keeps safe

  • Your files outside the project folder and the mounts.
  • Your API key, your git login and SSH keys, and your IDE’s token. Note that a subscription token and the secrets in env do go into the VM.
  • Your clipboard, apart from one image right after you press Ctrl+V. Set clipboard: false in the config to keep that out too.
  • Your machine’s own networks and the servers that listen on its loopback.
  • The rest of the internet. Data leaves only through what you allowed, like a host, a port or a branch.
  • The branches, tags and repositories the git list doesn’t name.
  • Your machine while the VM runs. The VM can’t run code on it directly. Code it writes into the project can run later. See below.
  • Your aibox config. It decides everything else on this list.

Why the config isn’t in the project

The project config decides what the VM gets: the hosts, the ports, the secrets, the mounts and the branches it can push to. So it lives on your machine, in ~/.aibox/projects/<escaped path>/config.yaml, and never in the project or its git repository. aibox doesn’t read a config from the project at all.

People ask for a config file in the repository, like a dev container’s devcontainer.json. aibox can’t have one, for two reasons:

  • A repository would pick its own permissions. Whoever wrote it, or slipped a change into it, would decide which hosts and secrets the VM gets. Running aibox run in a stranger’s repository would hand them your secrets.
  • The VM would rewrite its own config. With aibox run the VM can change anything in the project, .git included. A config there could grant more in one session and take effect at the next start. A task’s commits could carry one too, and you’d merge it with the rest of the change.

Your starting config for new projects, ~/.aibox/config.yaml, is out of reach of the VM too.

aibox reads the config once, when it starts. Nothing the VM does while it runs changes what it gets. To change the config, run aibox config edit on your machine.

Trust boundaries

Everything the VM can reach on your machine goes through one of these.

BoundaryWhat crosses itWhat aibox does
The hypervisorThe VM’s CPU, memory and devicesThe VM has its own kernel. On Linux it runs on QEMU in a bubblewrap sandbox. On macOS it runs on Virtualization.framework, which aibox can’t sandbox. On both, aibox locks itself down once the VM runs. See The host side.
The sharesThe project, the mounts, your skills, agents and commands, and the VM’s homeYour machine enforces the read-only shares. aibox won’t start in your home folder or above it, and won’t mount your home folder, a folder above it, or a folder in or above ~/.aibox. See mounts.
The proxyAll network traffic, over vsock or a serial portThe VM has no network card. The proxy only connects to the allow list and refuses names that lead to your own networks. See allow.
The git brokergit’s requestsThe broker adds your login on your machine, only for the repositories and branches the config names. See Git.
The key forwarderRequests to the Claude APIThe VM gets a placeholder key. aibox adds the real one. See API key.
The IDE bridgeSelections, diffs and errorsWith ide: true, the bridge passes on only those messages, and only for files in the project. See In your IDE.
The clipboardOne image after Ctrl+Vaibox lets the VM read the clipboard only right after you press Ctrl+V, once, and only an image. Tasks never get it. See Pasting images.
The terminalWhat Claude Code shows youaibox run passes the VM’s terminal straight through.
A task’s resultsThe bundle, the logs and result.jsonFor aibox task, aibox checks each file and the bundle, and strips control characters before text reaches your terminal. See How a task runs.
Your project folderWhat the VM writesaibox doesn’t check it. Treat it like a pull request from a stranger. See What the VM writes.

Attacks and what stops them

The VM tries toWhat stops it
read a file outside the projectThe VM only sees its shares.
get aibox to write a file outside through a symlink in the project or its homeaibox works in those folders in a helper that the kernel lets create, change and delete files only there. On Linux it can still change the permissions and owner of a file outside. See The host side.
give itself more hosts, secrets or branchesThe config isn’t in the project, and aibox reads it once when it starts.
write to a read-only mountYour machine enforces read-only, not the VM.
break out through a bug in its kernelThe VM has its own kernel, so a bug in it gives the attacker nothing on your machine. The kernel has no modules, no network drivers and no bpf(). The attacker then has to break the hypervisor too.
break out through a bug in the hypervisorOn Linux QEMU runs in bubblewrap with no network, only LD_LIBRARY_PATH in its environment and no writable file system, and filters its own syscalls. On macOS the VM runs in a Virtualization.framework process, which aibox can’t put in a sandbox of its own.
take over aibox through a bug in the proxy or the brokeraibox locks itself down once the VM runs. On Linux 6.7 or newer that’s Landlock and seccomp, on macOS Seatbelt.
send your data to a host of its ownThe proxy only connects to the allow list.
reach your router or a server on your machineThe proxy refuses a name that resolves to your own networks. A loopback port needs its address on the allow list.
read your API keyIt only has a placeholder.
read your git loginIt never enters the VM.
push to main, delete a branch or push a tagThe broker refuses what the git list doesn’t allow.
read the passwords you copyIt can only ask for the clipboard right after you press Ctrl+V, and only gets an image.
run code through your IDEThe IDE bridge only passes on selections, diffs and errors.
sneak extra branches into a task’s bundleaibox reads the bundle’s header and only prints the fetch command for aibox/task.
fake a line from aibox in a task’s outputaibox strips control characters and marks every line from the VM.
get a changed kernel or image bootedA release checks the SHA-256 of a downloaded image and of the kernel before each start. On macOS Virtualization.framework reads the kernel again after the check, so a change right after it isn’t caught.
keep running after aibox endsThe VM ends with aibox, even when aibox is killed.

What aibox doesn’t stop

These are on you. What aibox does not protect has the details.

  • What the VM writes into the project, .git included. A hook, .envrc or package.json can run on your machine later.
  • Code the VM wrote, once you run it on your machine. That includes tests your IDE runs by itself, like on save, and the program you’re working on when you build or start it. A test or a line in the program can do anything you can.
  • What you allowed. The VM can use every host, port, secret in env and branch you give it. A mount gives it every file in that folder. A subscription token goes into the VM as it is, so the VM can read it.
  • The servers on your loopback that you allow. They run as you, often without a login, and do what the VM asks. See Ports on your machine.
  • What goes through an allowed connection. proxy.log shows the host, not what was sent.
  • What a task says about itself. Claude Code’s last message, result.json and the authors of the commits come from the VM.
  • What the VM leaves for the next aibox run. The VM’s home folder and state disk stay between runs, so a steered session can leave something behind for the next one, like a line in ~/.claude/CLAUDE.md. aibox projects reset --logout clears both. See Starting over. Tasks get a new home and state disk each time.
  • What the VM shows in your terminal. With aibox run its output reaches your terminal as it is, escape sequences included.
  • Force pushes to an allowed branch, and the CI a pushed branch starts.
  • Diffs you accept or edit in your IDE.
  • Your clipboard right after a Ctrl+V, or Alt+V on Windows, in any program in the VM. aibox watches for the key, not for Claude Code, so a Ctrl+V in bash or vim lets the VM read the image too.
  • Gatekeeper on macOS, for programs that come out of the VM.
  • Spending. Only a task’s --timeout is enforced by aibox.

Tasks

aibox task runs in the same VM, behind the same proxy, broker and key forwarder. What changes is what the VM gets to touch and who is watching.

aibox runaibox task
What the VM seesyour project folder, writablea clone of one commit
Who approves each stepyounobody, because Claude Code has all permissions
What stays for the next runthe home and the state disknothing, because both are new each time
The clipboardafter Ctrl+V, unless clipboard: falsenever
What reaches your terminalthe VM’s output as it istext with control characters stripped, each line marked
What reaches your projectevery write, right awayonly the bundle, after aibox checks it, and only once you fetch it

So a task can’t touch your working tree or .git, and it leaves nothing behind in the VM. But nobody stops it from using everything the config gives it: every host, port, secret and branch. And what it hands back is code you haven’t read. Your review is the boundary of a task. Fetch the branch with the command aibox prints and read the whole diff before you check it out or merge it. See Reviewing the commits.

What aibox assumes

  • KVM, Virtualization.framework or Hyper-V holds. aibox makes a break-out harder, but on macOS it can’t sandbox the Virtualization.framework process. On Linux, --no-sandbox takes QEMU out of bubblewrap. On Windows it takes the helper that serves the proxy out of its AppContainer.
  • Your machine isn’t already taken over. A program that can change aibox, its config in ~/.aibox or the programs it starts is out of scope.
  • The release is the one we built. Check a download with cosign. See Installing. An image you pass with --image, and a build from a checkout, don’t check the kernel.
  • You read what comes out of the VM before you run it, commit it or merge it.