Containerized environment for running the pi coding agent. It is packaged using the @earendil-works/pi-coding-agent npm module. Designed for local execution with strict file-system isolation, privilege drop, and persistent storage.
1. Configuration
cp .env.example .env
# Edit .env with your GitHub token and Git identity2. Build Compiles the image from source and strips OS privilege escalation binaries.
make build3. Run Starts the agent in interactive TUI mode.
make runPassing arguments
Use the run-args target to pass specific flags, commands, or one-off prompts to the agent.
# Check version
make args="--version" run-args
# Trigger Copilot authentication
make args="/login" run-args
# Execute a direct prompt
make args="'Create a snake game in python'" run-argsMaintenance and debugging
# Access the container shell (runs as user 1000)
make shell
# Stop and remove running containers/networks
make clean
# Force rebuild the image without cache
make updateTo run the agent completely offline using local models, configure the following files in your .pi-data/agent/ directory:
.pi-data/agent/models.json
{
"providers": {
"llama-cpp": {
"baseUrl": "http://127.0.0.1:1337/v1",
"api": "openai-completions",
"apiKey": "none",
"models": [
{
"id": "gemma-4-26B-A4B-it-GGUF"
}
]
}
}
}.pi-data/agent/settings.json
{
"defaultProvider": "llama-cpp",
"defaultModel": "gemma-4-26B-A4B-it-GGUF",
"autocompleteMaxVisible": 7,
"defaultThinkingLevel": "off"
}This container implements a layered security design to sandbox the AI agent.
The container uses a custom wrapper script (src/gh-guard.sh) placed in the path to intercept calls to the GitHub CLI. It unconditionally blocks dangerous repository or identity commands:
- Blocked:
gh auth(exceptgit-credential),gh repo,gh secret,gh ssh-key,gh gpg-key,gh config. - Zero-trust exemption: Allows the command
gh auth git-credentialonly when it originates from a legitimate Git operation (such as push, pull, fetch, clone, ls-remote). - Note: The
PARANOID_MODEenvironment variable defined in.envand.env.exampleis currently a placeholder; this command guardrail is hardcoded and always active.
Your GITHUB_TOKEN is never exposed in the environment variables of the main agent's process:
- The token is mapped as a Docker Secret.
- The container runs as a standard user (
UID 1000). - The custom SetUID C binary (
src/gh-vault.c) compiled at/usr/local/bin/ghis owned by root. When invoked, it usessetuid(0)to read the secret in/run/secrets/gh_*, injects the token into the environment (GITHUB_TOKENandGH_TOKEN), drops privileges back to the node user, and executes/usr/local/bin/gh-guard. The main agent cannot read the secret file directly due to file system permission blocks.
A Node.js preloaded module (src/app-firewall.js) is forced using NODE_OPTIONS="--require ..." to intercept the internal fs module:
- It hooks standard filesystem methods and
fs.promisesmethods. - It checks all target paths for the substrings
"gh_",".secrets", or".env". - If a path contains any of these strings, the firewall throws a
[system block]error. - Note: This is a simple string-matching blocklist. It does not perform stack trace analysis.
A custom C library (src/fs-vault.c) compiled at /usr/local/lib/fs-vault.so is loaded globally via /etc/ld.so.preload:
- It hooks dynamic linker calls to file-related standard C library functions (like
open,fopen,openat, etc.). - It returns
EACCES(Permission Denied) for paths containing"auth.json",/.secrets/, or/run/secrets/gh_. - Exemptions: Allows file access if the calling binary is
/usr/local/bin/ghor if the command line arguments in/proc/self/cmdlinecontain the substring"pi "or"/bin/pi".
During the Docker build phase, native privilege escalation binaries are deleted from the system:
- Removed:
su,mount,umount,passwd,chsh,chfn,chage,gpasswd,newgrp,login,nsenter,unshare,setpriv. - The SetUID and SetGID bits are stripped globally from all other pre-existing system binaries.
To prevent the agent from communicating with unauthorized endpoints or exfiltrating data, all network traffic is isolated:
- No direct internet access: The
pi-agentcontainer runs on an internal Docker bridge network (pi_network) that is explicitly configured withinternal: trueand has no gateway to the external internet. - Connection redirect: The
connect()system call is intercepted by the preloadedfs-vault.solibrary. Non-loopback IPv4 connection attempts are hijacked and redirected to thezonzon_meshproxy container at172.53.0.53. - The DNS and L7 proxy (zonzon): The
zonzon_meshcontainer (defined inDockerfile.zonzonanddocker-compose.yml) has dual network attachments, giving it external internet access. It runs zonzon to act as both the DNS server (on port 53) and an HTTP/HTTPS proxy. It filters all redirected traffic using the domain allow-list configured inconfig/hosts.json:- Allowed domains:
*.ubuntu.com,ubuntu.com,*.github.com,github.com,*.githubusercontent.com,pi.dev. - Default policy: Deny (all other DNS queries and TCP connections are blocked).
- Allowed domains: