pi-enclave
VM-isolated sandbox for pi with automatic secret protection · from yapp
Package details
Install pi-enclave from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-enclave- Package
pi-enclave- Version
0.3.0- Published
- Apr 10, 2026
- Downloads
- 64/mo · 11/wk
- Author
- mgabor3141
- License
- MIT
- Types
- extension
- Size
- 50.9 KB
- Dependencies
- 4 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"dist/index.js"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-enclave
From yapp · yet another pi pack
VM-isolated enclave for pi. Runs all tools inside a Gondolin micro-VM so secrets never enter the agent's execution environment.
pi install npm:pi-enclave
Requires QEMU: brew install qemu (macOS) or sudo apt install qemu-system-aarch64 (Linux).
How it works
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.
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.
┌─────────────────────────────────────────────────────┐
│ Gondolin VM (Alpine Linux) │
│ │
│ /home/user/project ← bidirectional mount │
│ GH_TOKEN = "GONDOLIN_SECRET_a8f3..." (placeholder) │
│ All pi tools execute here │
└──────────────────────────┬──────────────────────────┘
│ HTTP
▼
┌─────────────────────────────────────────────────────┐
│ HTTP proxy (host-side) │
│ placeholder → real value (only for allowed hosts) │
└─────────────────────────────────────────────────────┘
Getting started
/enclave init
This creates:
~/.pi/agent/extensions/pi-enclave.toml— global config (env vars, base packages)~/.pi/agent/extensions/pi-enclave.d/— drop-in files (git, jj, GitHub).pi/enclave.toml— project config withenabled = true
Once enabled, all tools (bash, read, write, edit) execute inside the VM automatically.
Drop-in files
Service integrations live in pi-enclave.d/ as self-contained TOML files. Each can contribute packages, setup scripts, secrets, and host policies. Delete a file to disable that integration.
~/.pi/agent/extensions/
├── pi-enclave.toml # base config: curl, jq, env vars
└── pi-enclave.d/
├── git.toml # git + user identity
├── github.toml # github-cli + secrets + policies
└── jj.toml # jujutsu + user identity
Example drop-in (git.toml):
packages = ["git"]
setup = """
git config --global safe.directory '*'
git config --global user.name "$USER_NAME"
git config --global user.email "$USER_EMAIL"
"""
USER_NAME and USER_EMAIL are defined in the main config as env vars resolved from the host:
[env]
USER_NAME = { command = "git config --global user.name" }
USER_EMAIL = { command = "git config --global user.email" }
Configuration
Image
Use a custom Gondolin image when you need a different base environment or a larger root filesystem. Build and tag the image separately, then reference it here:
image = "pi-enclave-large:latest"
Env vars
Non-secret values available in the VM and setup scripts. Three source types:
[env]
EDITOR = "vim" # static
USER_NAME = { command = "git config user.name" } # host command
GOPATH = { env = "GOPATH" } # host env var
Secrets
Like env vars, but values never enter the VM. The HTTP proxy injects them on the wire.
[secrets.GH_TOKEN]
command = "gh auth token"
hosts = ["api.github.com", "github.com", "*.githubusercontent.com"]
Git credentials
Configures git credential helpers using secret placeholders:
[[git-credentials]]
host = "github.com"
username = "x-access-token"
secret = "GH_TOKEN"
Host policies
Access control per host. unmatched determines what happens to requests that don't match any allow/deny rule.
[hosts."api.github.com"]
unmatched = "prompt"
allow.GET = ["/**"]
[hosts."api.github.com".graphql]
endpoint = "/graphql"
allow.query = ["*"]
allow.mutation = ["createPullRequest", "createIssue", "addComment"]
GraphQL policy parses the request body and checks actual field names (not the spoofable operation name).
Mounts
The workspace directory is mounted automatically. Use mounts for directories outside it, like a jj/git repo root that is a parent of the workspace. Relative paths are resolved against the workspace directory. Missing paths are silently skipped, so optional mounts are safe to declare unconditionally:
# In .pi/enclave.toml (project config)
mounts = ["../.jj", "../.git"]
Absolute and ~-prefixed paths also work (useful in global config):
mounts = ["~/shared/data"]
For read-only mounts, use the object form:
mounts = [
"../.jj",
{ path = "~/shared/configs", readonly = true },
]
Config layering
Two locations: global (~/.pi/agent/extensions/pi-enclave.toml + drop-ins) and project (.pi/enclave.toml). Project overrides global. image uses the last configured value. Packages accumulate across all layers; secrets, hosts, and env merge by key (later wins).
# .pi/enclave.toml — allow all GitHub operations in this project
enabled = true
[hosts."api.github.com"]
unmatched = "allow"
Commands
| Command | Description |
|---|---|
/enclave or /enclave status |
Show VM state, packages, secrets |
/enclave init |
Create project and global config files, enable enclave |
/enclave on |
Enable VM isolation for this session |
/enclave off |
Disable VM isolation for this session (shuts down VM) |
/enclave restart |
Restart VM on next tool use |
/enclave add <package> |
Search for and install an Alpine package |