A sandbox for the code your agents run.
grix is a small Linux-compatible kernel, written in Rust. Every run boots a fresh microVM that runs exactly one unmodified Linux program. The program sees only the files you hand it, and can do only what you grant it. Anything else is denied, and grix tells your agent exactly what was missing.
# Only /bin/net exists, and nothing is granted. $ grix run -i ./net:/bin/net -- /bin/net socket: Operation not permitted grix: denied socket: needs capability `net` # Grant it, and the program gets its socket. $ grix run -a net -i ./net:/bin/net -- /bin/net socket: opened fd 3
And this is that program, in grix, running in your browser. Run it, then tick net and run it
again.
Your browser didn't give this page cross-origin isolation, so the demos can't run here. They need SharedArrayBuffer, for QEMU's threads.
One program, one VM, nothing it wasn't given
grix (greg + unix) is an agentbox: a unikernel built to run Linux programs on behalf of AI agents, with least privilege by default.
A fresh microVM per run
Each run boots grix's kernel in its own virtual machine, on Hypervisor.framework or KVM. Booting, running a small program and exiting takes about a tenth of a second.
Only what you import
The filesystem is the files you list, and the environment is the variables you set. The VM has no network card and no disk. The program can't reach anything you didn't give it.
Denied, and reported
Writing files, threads, processes and the network are capabilities you grant. Anything else fails
the way it would on Linux, and grix reports it: denied socket: needs capability `net`.
Agents run code. Something has to say no.
An agent that writes or downloads a program has to run it somewhere. On your machine, it can do anything you can. A container shares your kernel, and inside it everything is allowed. A full VM is a whole operating system to boot and maintain. grix starts from nothing allowed, starts in a tenth of a second, and answers in a form an agent can act on.
- Least privilege by default No files, no network, no processes, no threads until you grant them. Privileged calls (mount, ptrace, bpf, modules) are never available.
- Every refusal is explained
Denied calls, files the program looked for and syscalls grix doesn't implement yet all come back
as events. An agent can ask you for
netand run it again. - Real software, unmodified Python 3.12, Node 22, Go, busybox, and Debian's bash, coreutils and git. arm64 and x86_64 Linux binaries, musl or glibc, run as they are.
- Fast to start, small to run About 0.1 s to boot, run and exit. Snapshots resume a warmed-up Python in 0.13 s. You can import a single static binary, with no OS image at all.
- Networking under an allow-list
With
net, the guest's sockets become messages to the host. The host makes each connection only if your rules allow that host and port. - Built for agents An MCP server, Python and Rust libraries, JSON output, and images built from Docker/OCI images.
Run it here
These demos run the real grix kernel in your browser, on QEMU compiled to WebAssembly. Edit a command, grant a capability, and run it again.
Your browser didn't give this page cross-origin isolation, so the demos can't run here. They need SharedArrayBuffer, for QEMU's threads.
A shell pipeline, without and then with process
bash, in a terminal
Try ls -l / | head, sleep 100 then Ctrl-C, or echo hi > /tmp/x
(that needs fs_write).
Python's own REPL
Try import os; os.listdir('/'), or open('/tmp/x', 'w') without
fs_write.
All the demos: Go, Node, stdin, missing files and more.
Get grix
Every commit to main is a release: grix, its
VMM, its kernels and the Python package, in one archive for each platform. Nothing else to install.
-
Download and check it
Pick your platform:
macos-arm64(Apple silicon, macOS 15+),linux-arm64orlinux-x86_64.os=macos-arm64 tag=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/gmmeyer/grix/releases/latest | sed 's|.*/tag/||') v=${tag#v} curl -fsSLO "https://github.com/gmmeyer/grix/releases/download/$tag/grix-$v-$os.tar.gz" curl -fsSLO "https://github.com/gmmeyer/grix/releases/download/$tag/SHA256SUMS" grep " grix-$v-$os.tar.gz" SHA256SUMS | shasum -a 256 -c # or: sha256sum -c tar xzf "grix-$v-$os.tar.gz" -
Put it on your PATH
grix finds its kernel and VMM beside itself, so keep the directory together.
mkdir -p ~/.local && mv "grix-$v-$os" ~/.local/grix export PATH="$HOME/.local/grix:$PATH" # and in your shell's profile grix --version -
Run something
Turn Docker Hub's Alpine into a grix image, then run its shell. It can't start
catuntil you grantprocess, and it can't reach the network until you grantnetfor one host.grix build --from alpine:3.21 -o alpine.grix grix run --image alpine.grix -- /bin/sh -c 'cat /etc/alpine-release' /bin/sh: cat: Operation not permitted grix: denied execve: needs capability `process` grix run --image alpine.grix -a process -- /bin/sh -c 'cat /etc/alpine-release' 3.21.9 grix run --image alpine.grix -a process -a net --allow-host example.com:80 -- \ /bin/sh -c 'wget -qO- http://example.com | grep -o "<title>.*</title>"' <title>Example Domain</title>
Platform notes
- macOS: Apple silicon and macOS 15 or later. If you downloaded the archive in a
browser instead of with
curl, clear the quarantine first:xattr -d com.apple.quarantine ~/.local/grix/grix ~/.local/grix/grix-vmm. - Linux: grix's own VMM needs
/dev/kvm. Add yourself to thekvmgroup (sudo usermod -aG kvm $USER, then log in again), or pass--device /dev/kvmto a container. Without KVM, grix falls back to QEMU (qemu-system-aarch64orqemu-system-x86_64), which emulates and is much slower. - x86_64 Linux: images built from Docker/OCI images are arm64 for now. Set
GRIX_ARCH=aarch64forgrix buildandgrix run, and they run emulated inqemu-system-aarch64. x86_64 programs you import yourself run natively on KVM. - From source:
git clone https://github.com/gmmeyer/grix && cd grix && makeneeds Rust, and zig and Go for the test programs. See the README.
From the command line, an agent, or your code
The same sandbox from the CLI, an MCP server for agents (claude mcp add grix --
grix mcp --root "$PWD" in Claude Code), Python (PYTHONPATH=~/.local/grix/python, Python
3.9+), or Rust.
# Only /bin/net exists, and nothing is granted. $ grix run -i ./net:/bin/net -- /bin/net socket: Operation not permitted grix: denied socket: needs capability `net` # Python, with threads, and the network only to example.com. $ grix run -a threads -a net --allow-host example.com:443 \ -i ./python-rootfs:/ -- /usr/bin/python3 script.py
// Claude Code, or any MCP client: a `run` tool
// that runs programs in fresh sandboxes.
{"mcpServers": {"grix": {
"command": "grix",
"args": ["mcp", "--root", "/path/to/project"]
}}}import grix
sb = grix.Sandbox(imports={"./busybox": "/bin/busybox"},
files={"/data/in.txt": "b\na\n"},
capabilities=["process"], timeout=30)
r = sb.run(["/bin/busybox", "sort", "/data/in.txt"])
print(r.exit_code, r.stdout, r.events, r.missing_capabilities)let sandbox = Sandbox::builder()
.import("./tools/busybox.static", "/bin/busybox")?
.file("/data/input.csv", csv_bytes, 0o644)?
.allow(Capability::FsWrite)
.export("/out")?
.build()?;
let out = sandbox.run(&[
"/bin/busybox", "sort", "-o", "/out/sorted.csv", "/data/input.csv",
])?;
let needed = out.missing_capabilities();A kernel for one program
Your process (the host)
- collects the imports, argv, env and policy into a boot image
- starts the VMM: grix-vmm, or QEMU
- reads output, events and the exit status
- makes network connections the policy allows
microVM: the grix kernel
- no_std Rust, booted fresh for every run
- builds a filesystem from the imports, in memory
- implements Linux's system calls itself, checking each against the policy
- runs your program, unmodified
The hypervisor is the wall. A program that took over the grix kernel would still be in a VM with no network card, no disk, and only its imports in memory.
A small, confined VMM. grix-vmm, on Hypervisor.framework or KVM, emulates only the few devices grix needs. Once the VM is up it confines itself: no files, no network, no new processes.
No network card. Sockets become messages on the console. The host checks each one against your allow-list, makes the connection, and answers DNS.
Host directories without a disk. Live mounts are served by the host over the console, file by file, as the program asks.
What a program gets
| Capability | Without it | With it |
|---|---|---|
| (always) | Memory, time, randomness, stdout/stderr, pipes, epoll, local sockets, and read access to the imported files | |
| fs_write | The filesystem is read-only | The whole tree is writable in guest memory, copy-on-write over the imports. The host's files never change. |
| threads | No new threads | Threads, with futexes and preemption |
| process | No fork, exec or wait | Processes, shell pipelines, subprocess, os/exec |
| net | No sockets, and the VM has no network device | TCP and DNS through the host, only where the allow-list says, and forwarded ports for servers |
| (never) | Privileged calls: mount, ptrace, bpf, module loading, Linux capabilities | |
Status: early prototype. grix runs a lot of real software, but it isn't hardened against a determined attacker yet. See the security model and what's compatible.
The Future of Software Engineering
Unikernels lost to containers because they asked too much of developers. What happens when friction goes to zero? Why grix exists, with the demos running in the post.