Skip to content

Commit b043b91

Browse files
JAORMXclaude
andcommitted
Add comprehensive project README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5fe7449 commit b043b91

1 file changed

Lines changed: 323 additions & 0 deletions

File tree

README.md

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# apiary
2+
3+
Run coding agents in hardware-isolated microVMs. Review every change before it touches your workspace.
4+
5+
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
6+
[![Go Report Card](https://goreportcard.com/badge/github.com/stacklok/apiary)](https://goreportcard.com/report/github.com/stacklok/apiary)
7+
8+
<!-- TODO: Add a terminal recording / GIF demo here showing the full workflow -->
9+
10+
## Why?
11+
12+
Coding agents are powerful, but they need access to your workspace, your API keys,
13+
and the ability to run arbitrary code. That's a lot of trust to hand over.
14+
15+
Containers help, but they share the host kernel. One escape and you're done.
16+
17+
Enter **apiary**. It boots a lightweight microVM (via [libkrun](https://github.com/containers/libkrun) and KVM),
18+
mounts a copy-on-write snapshot of your workspace, forwards only the secrets you
19+
specify, and lets you review every file change before it lands. Hardware isolation
20+
with the feel of a local terminal.
21+
22+
```bash
23+
apiary claude-code
24+
```
25+
26+
And that's it. You get a full interactive session with Claude Code running inside a
27+
VM. When the agent exits, you review the diff and accept or reject each file.
28+
29+
## Features
30+
31+
- **Hardware-isolated microVMs** -- KVM-backed VMs via libkrun, not just containers
32+
- **Workspace snapshot & review** -- COW snapshot so the agent never touches your real files; interactive per-file review with unified diffs when it's done
33+
- **Multi-agent support** -- Claude Code, Codex, and OpenCode out of the box, plus custom agents via config
34+
- **DNS-aware egress firewall** -- Three profiles (permissive, standard, locked) control what the VM can reach
35+
- **MCP tool proxy** -- Automatically discovers and proxies [ToolHive](https://github.com/stacklok/toolhive) MCP servers into the VM
36+
- **Git integration** -- Forwards tokens and SSH agent for git operations inside the VM
37+
- **Ephemeral security** -- Per-session SSH keys, localhost-only connections, non-overridable security patterns for sensitive files
38+
- **Zero persistent state** -- Each session is fully ephemeral; nothing lingers after cleanup
39+
40+
## Quick Start
41+
42+
### Prerequisites
43+
44+
- Linux with KVM support (`/dev/kvm` must be accessible)
45+
- [Go 1.25.7+](https://go.dev/dl/)
46+
- [Task](https://taskfile.dev/) (task runner)
47+
- [libkrun-devel](https://github.com/containers/libkrun) installed
48+
- [propolis](https://github.com/stacklok/propolis) checked out at `../propolis` relative to apiary
49+
- An API key for your agent (e.g. `ANTHROPIC_API_KEY` for Claude Code)
50+
51+
### Build
52+
53+
```bash
54+
task build-dev
55+
```
56+
57+
This compiles the `apiary` binary (pure Go, no CGO) and the `propolis-runner`
58+
binary (requires libkrun-devel). Both land in `bin/`.
59+
60+
### Run
61+
62+
```bash
63+
export ANTHROPIC_API_KEY="sk-ant-..."
64+
apiary claude-code
65+
```
66+
67+
The workflow:
68+
69+
1. Creates a COW snapshot of your current directory
70+
2. Boots a microVM with the Claude Code image
71+
3. Drops you into an interactive terminal session
72+
4. When you exit, shows a per-file diff review
73+
5. Accepted changes are flushed back to your workspace
74+
75+
## Usage
76+
77+
```bash
78+
# Run with a specific agent
79+
apiary claude-code
80+
apiary codex
81+
apiary opencode
82+
83+
# Override resources
84+
apiary claude-code --cpus 4 --memory 4096
85+
86+
# Use a different workspace
87+
apiary claude-code --workspace /path/to/project
88+
89+
# Disable snapshot isolation (mount workspace directly)
90+
apiary claude-code --no-review
91+
92+
# Exclude files from snapshot
93+
apiary claude-code --exclude "*.log" --exclude "tmp/"
94+
95+
# Lock down egress to LLM provider only
96+
apiary claude-code --egress-profile locked
97+
98+
# Allow additional egress hosts
99+
apiary claude-code --allow-host "internal-api.example.com:443"
100+
101+
# Disable MCP proxy
102+
apiary claude-code --no-mcp
103+
104+
# Use a specific ToolHive group for MCP servers
105+
apiary claude-code --mcp-group "coding-tools"
106+
107+
# List available agents
108+
apiary list
109+
```
110+
111+
## Configuration
112+
113+
Apiary uses a three-level config system: CLI flags > per-workspace > global. CLI flags always win.
114+
115+
### Global config
116+
117+
`~/.config/apiary/config.yaml`:
118+
119+
```yaml
120+
defaults:
121+
cpus: 4
122+
memory: 4096
123+
egress_profile: "standard"
124+
125+
review:
126+
enabled: true
127+
exclude_patterns:
128+
- "*.log"
129+
- "build/"
130+
131+
mcp:
132+
enabled: true
133+
group: "default"
134+
port: 4483
135+
136+
git:
137+
forward_token: true
138+
forward_ssh_agent: true
139+
140+
agents:
141+
claude-code:
142+
env_forward:
143+
- ANTHROPIC_API_KEY
144+
- CLAUDE_*
145+
- GITHUB_TOKEN
146+
```
147+
148+
### Per-workspace config
149+
150+
`.apiary.yaml` in your project root:
151+
152+
```yaml
153+
defaults:
154+
cpus: 8
155+
memory: 8192
156+
157+
review:
158+
exclude_patterns:
159+
- "data/"
160+
```
161+
162+
Note that `review.enabled` is **ignored** in per-workspace config for security.
163+
An untrusted repo can't disable review on your behalf.
164+
165+
Similarly, `egress_profile` in per-workspace config cannot widen the global profile.
166+
167+
### Exclude patterns
168+
169+
`.apiaryignore` in your project root uses gitignore syntax:
170+
171+
```gitignore
172+
# Exclude build artifacts
173+
build/
174+
dist/
175+
176+
# But include the config
177+
!dist/config.json
178+
```
179+
180+
Security-sensitive patterns (`.env*`, `*.pem`, `.ssh/`, `.aws/`, etc.) are **always excluded** and cannot be negated.
181+
182+
## Egress Firewall
183+
184+
Each agent comes with DNS-aware egress policies. Three profiles are available:
185+
186+
| Profile | What it allows |
187+
|---|---|
188+
| `permissive` | All outbound traffic, no restrictions |
189+
| `standard` (default) | LLM provider + common dev infrastructure (GitHub, npm, PyPI, Go proxy, Docker Hub, GHCR) |
190+
| `locked` | LLM provider only (e.g. `api.anthropic.com` for Claude Code) |
191+
192+
```bash
193+
# Lock it down
194+
apiary claude-code --egress-profile locked
195+
196+
# Or open it up
197+
apiary claude-code --egress-profile permissive
198+
199+
# Add specific hosts to standard profile
200+
apiary claude-code --allow-host "my-registry.example.com:443"
201+
```
202+
203+
## Supported Agents
204+
205+
| Agent | Command | Image | Default Resources |
206+
|---|---|---|---|
207+
| Claude Code | `apiary claude-code` | `ghcr.io/stacklok/apiary/claude-code` | 2 vCPUs, 2 GiB RAM |
208+
| Codex | `apiary codex` | `ghcr.io/stacklok/apiary/codex` | 2 vCPUs, 2 GiB RAM |
209+
| OpenCode | `apiary opencode` | `ghcr.io/stacklok/apiary/opencode` | 2 vCPUs, 2 GiB RAM |
210+
211+
You can also define custom agents in your config:
212+
213+
```yaml
214+
agents:
215+
my-agent:
216+
image: "ghcr.io/my-org/my-agent:latest"
217+
command: ["my-agent-binary"]
218+
cpus: 4
219+
memory: 4096
220+
env_forward:
221+
- MY_API_KEY
222+
- MY_AGENT_*
223+
```
224+
225+
## How It Works
226+
227+
```
228+
apiary claude-code
229+
230+
231+
Create COW snapshot of workspace
232+
233+
234+
Pull OCI image, extract rootfs, inject init binary + SSH keys
235+
236+
237+
Boot microVM (libkrun/KVM) with virtio-fs workspace mount
238+
239+
240+
Guest boots (apiary-init as PID 1):
241+
→ Mount filesystems, configure networking
242+
→ Start embedded SSH server
243+
→ Wait for connection
244+
245+
246+
Interactive SSH session:
247+
source /etc/sandbox-env && cd /workspace && exec claude
248+
249+
250+
Agent exits → VM stopped
251+
252+
253+
SHA-256 diff → Interactive per-file review → Flush accepted changes
254+
255+
256+
Cleanup snapshot
257+
```
258+
259+
The guest VM runs a custom Go init binary (`apiary-init`) as PID 1. No shell scripts,
260+
no external sshd, no iproute2. Everything the guest needs is compiled into a single
261+
binary that handles boot, networking, workspace mounting, and an embedded SSH server.
262+
263+
The workspace snapshot uses FICLONE on Linux for near-instant copy-on-write cloning.
264+
When the agent is done, a SHA-256 based differ detects changes, and the review UI
265+
shows unified diffs for each file. Accepted changes are flushed back with hash
266+
re-verification to prevent TOCTOU attacks. The VM is explicitly stopped before
267+
review begins, so the agent can't modify files during your review.
268+
269+
## Security Model
270+
271+
Apiary's isolation is built on several layers:
272+
273+
- **KVM hardware virtualization** -- The agent runs in a real VM, not a container with a shared kernel
274+
- **Ephemeral SSH keys** -- ECDSA P-256 keys generated per session, destroyed on exit
275+
- **Localhost-only networking** -- SSH port forwards bind to 127.0.0.1 only
276+
- **Non-overridable security patterns** -- Files like `.env`, `*.pem`, `.ssh/`, `.aws/` are always excluded from snapshots, even if `.apiaryignore` tries to negate them
277+
- **Shell-escaped environment injection** -- All forwarded values are single-quote escaped
278+
- **VM stopped before review** -- Prevents the agent from modifying files while you're reviewing
279+
- **Hash verification on flush** -- Files are re-hashed between diff and flush to catch any modifications
280+
- **Permission stripping** -- setuid, setgid, and sticky bits are stripped when flushing changes
281+
- **Path traversal protection** -- Symlinks are validated in-bounds before copying
282+
- **Per-workspace config restrictions** -- `review.enabled` and egress widening are ignored in `.apiary.yaml`
283+
284+
## Building from Source
285+
286+
```bash
287+
# Build everything (apiary + propolis-runner)
288+
task build-dev
289+
290+
# Build apiary only (pure Go, no CGO)
291+
task build
292+
293+
# Build guest init binary
294+
task build-init
295+
296+
# Run tests
297+
task test
298+
299+
# Lint
300+
task lint
301+
302+
# Format + lint + test
303+
task verify
304+
305+
# Build guest VM images (requires podman)
306+
task image-all
307+
```
308+
309+
Note: Always use `task` for building, testing, and linting. The Taskfile sets
310+
critical flags, ldflags, and environment variables that raw `go` commands miss.
311+
312+
## Contributing
313+
314+
Contributions are welcome! Please open an issue to discuss your idea before submitting a PR.
315+
316+
The project follows strict DDD (Domain-Driven Design) layered architecture.
317+
See [CLAUDE.md](CLAUDE.md) for architecture details and coding conventions.
318+
319+
## License
320+
321+
[Apache-2.0](LICENSE)
322+
323+
Copyright 2025 [Stacklok, Inc.](https://stacklok.com)

0 commit comments

Comments
 (0)