Skip to content

Commit fee27d3

Browse files
committed
feat: add pi-enclave VM sandbox with secret protection
1 parent d03b1db commit fee27d3

20 files changed

Lines changed: 2633 additions & 1 deletion

.changeset/add-pi-enclave.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"pi-enclave": minor
3+
---
4+
5+
Initial release of pi-enclave: VM-isolated sandbox with automatic secret protection.
6+
7+
- All tools (bash, read, write, edit) execute inside a Gondolin micro-VM
8+
- Config-driven secrets with command and env sources, no auto-detection
9+
- Cascading TOML config: global, drop-in directory (pi-enclave.d/), project
10+
- Per-host HTTP policies with allow/deny/prompt for method+path patterns
11+
- GraphQL-aware policy: parses request bodies with the graphql package, checks actual field names (not spoofable operation names)
12+
- Ships with GitHub drop-in policy: reads allowed, common mutations allowed, destructive operations prompt
13+
- `/enclave init` creates global config + github.toml drop-in + project config
14+
- `/enclave on|off` for session toggles, `/enclave add` for interactive package install

packages/enclave/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# pi-enclave
2+
3+
> From [yapp](https://github.com/mgabor3141/yapp) · yet another pi pack
4+
5+
VM-isolated sandbox for [pi](https://pi.dev). Runs all tools inside a [Gondolin](https://github.com/earendil-works/gondolin) micro-VM so secrets never enter the agent's execution environment.
6+
7+
```bash
8+
pi install npm:pi-enclave
9+
```
10+
11+
Requires QEMU: `brew install qemu` (macOS) or `sudo apt install qemu-system-aarch64` (Linux).
12+
13+
## How it works
14+
15+
pi-enclave starts an Alpine Linux micro-VM (QEMU/aarch64) and redirects all tool execution into it. Your workspace is mounted read-write at the same path inside the VM, so tools see identical paths on host and guest. File changes are bidirectional.
16+
17+
The core security property: **secrets never enter the VM**. Secrets configured in your TOML config (like `gh auth token`) are resolved on the host, and their values are replaced with random placeholders inside the VM. Gondolin's HTTP proxy substitutes real values on the wire, only for requests to configured hosts.
18+
19+
```
20+
┌──────────────────────────────────────────────────┐
21+
│ Gondolin VM (Alpine Linux) │
22+
│ │
23+
│ /home/user/project ← bidirectional mount │
24+
│ GH_TOKEN = "GONDOLIN_SECRET_a8f3..." (placeholder)│
25+
│ All pi tools execute here │
26+
└────────────────────┬─────────────────────────────┘
27+
│ HTTP
28+
29+
┌──────────────────────────────────────────────────┐
30+
│ HTTP proxy (host-side) │
31+
│ placeholder → real value (only for allowed hosts)│
32+
└──────────────────────────────────────────────────┘
33+
```
34+
35+
## Getting started
36+
37+
After installing, run `/enclave init` in any project to enable it:
38+
39+
```
40+
/enclave init
41+
```
42+
43+
This creates two files:
44+
- `~/.pi/agent/extensions/pi-enclave.toml` — global defaults (GitHub policy, secrets)
45+
- `.pi/enclave.toml` — project config with `enabled = true`
46+
47+
Once enabled, all tools (bash, read, write, edit) execute inside the VM automatically.
48+
49+
## Default behavior
50+
51+
With `/enclave init`, pi-enclave creates a global config with sensible defaults:
52+
53+
1. Starts a VM, mounts your project directory
54+
2. Installs `git`, `curl`, `jq`, `github-cli`
55+
3. Resolves `GH_TOKEN` via `gh auth token` (skipped if `gh` is not installed)
56+
4. GitHub API policy: GET and GraphQL queries are allowed, mutations and other writes prompt for approval
57+
5. GraphQL policy parses the request body and checks actual field names (not the spoofable operation name)
58+
6. Redirects all tool execution to the VM
59+
60+
## Configuration
61+
62+
Place `.pi/enclave.toml` in your project (or any ancestor directory).
63+
64+
```toml
65+
# Enable VM isolation for this project
66+
enabled = true
67+
68+
# Alpine packages to install
69+
# packages = ["git", "curl", "jq", "ripgrep", "github-cli"]
70+
71+
# Secrets: resolved on the host, injected via HTTP proxy
72+
[secrets.GH_TOKEN]
73+
command = "gh auth token"
74+
hosts = ["api.github.com", "github.com", "*.githubusercontent.com"]
75+
76+
# [secrets.OPENAI_API_KEY]
77+
# env = "OPENAI_API_KEY"
78+
# hosts = ["api.openai.com"]
79+
80+
# Disable a secret inherited from global config
81+
# [secrets.GH_TOKEN]
82+
# false
83+
84+
# Host policies: access control for specific hosts
85+
[hosts."api.github.com"]
86+
unmatched = "prompt"
87+
allow.GET = ["/**"]
88+
89+
# GraphQL: parses request body, checks actual field names
90+
[hosts."api.github.com".graphql]
91+
endpoint = "/graphql"
92+
allow.query = ["*"]
93+
allow.mutation = [
94+
"createPullRequest",
95+
"createIssue",
96+
"addComment",
97+
]
98+
```
99+
100+
Config files cascade: global (`~/.pi/agent/extensions/pi-enclave.toml`) is loaded first, then `.pi/enclave.toml` files from ancestor directories (closest wins). Config inside the workspace is protected from modification by the VM via Gondolin's `ShadowProvider`.
101+
102+
### Project overrides
103+
104+
A project can loosen restrictions from the global config:
105+
106+
```toml
107+
# .pi/enclave.toml
108+
enabled = true
109+
110+
# Allow all GitHub API operations without prompts
111+
[hosts."api.github.com"]
112+
unmatched = "allow"
113+
```
114+
115+
## Commands
116+
117+
| Command | Description |
118+
|---------|-------------|
119+
| `/enclave` or `/enclave status` | Show VM state, packages, secrets |
120+
| `/enclave init` | Create project and global config files, enable enclave |
121+
| `/enclave on` | Enable VM isolation for this session |
122+
| `/enclave off` | Disable VM isolation for this session (shuts down VM) |
123+
| `/enclave restart` | Restart VM on next tool use |
124+
| `/enclave add <package>` | Search for and install an Alpine package |

packages/enclave/package.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "pi-enclave",
3+
"version": "0.0.1",
4+
"description": "VM-isolated sandbox for pi with automatic secret protection · from yapp",
5+
"author": "mgabor3141",
6+
"license": "MIT",
7+
"repository": {
8+
"url": "git+https://github.com/mgabor3141/yapp.git",
9+
"directory": "packages/enclave"
10+
},
11+
"keywords": [
12+
"pi-package",
13+
"pi-extension",
14+
"yapp"
15+
],
16+
"type": "module",
17+
"main": "dist/index.js",
18+
"types": "dist/index.d.ts",
19+
"exports": {
20+
".": {
21+
"import": "./dist/index.js",
22+
"types": "./dist/index.d.ts"
23+
}
24+
},
25+
"files": [
26+
"dist",
27+
"templates",
28+
"README.md"
29+
],
30+
"scripts": {
31+
"build": "tsup"
32+
},
33+
"pi": {
34+
"extensions": [
35+
"dist/index.js"
36+
]
37+
},
38+
"dependencies": {
39+
"@earendil-works/gondolin": "^0.6.0",
40+
"graphql": "^16.13.1",
41+
"smol-toml": "^1.6.0",
42+
"valibot": "^1.2.0"
43+
},
44+
"peerDependencies": {
45+
"@mariozechner/pi-coding-agent": "*"
46+
},
47+
"devDependencies": {
48+
"@mariozechner/pi-coding-agent": "^0.57.0",
49+
"@types/node": "^25.3.5",
50+
"tsup": "^8.5.1",
51+
"typescript": "^5.9.3"
52+
}
53+
}

0 commit comments

Comments
 (0)