pi-sandbox-docker
v0.1.0
Published
On-demand disposable Docker sandbox for Pi: run code in a fresh, offline Debian container with read-only project mount and per-run network approval.
Maintainers
Readme
pi-sandbox-docker
On-demand, disposable Docker sandbox for Pi. The Pi Coding
Agent gets a sandbox_run tool that executes code inside a fresh, hardened,
offline Debian container instead of on the host.
Every sandbox_run call creates a brand-new container and destroys it when the
run ends — including on failure, timeout, and cancellation. No containers,
volumes, or package caches persist between runs.
Why
- Isolation — model-generated code runs away from your host filesystem, shells, credentials, and network.
- Fresh state — nothing leaks between runs; no pip/npm/apt caches survive.
- Read-only project — your repo is mounted read-only at
/workspace. - Offline by default — networking is off unless a human approves a single run.
Prerequisites
- Pi (see https://pi.dev/docs/latest)
- Docker CLI with a reachable daemon (
docker psworks) - The sandbox image (build with
/sandbox-build)
Installation
pi install npm:pi-sandbox-dockerFor local development:
# from this repo
pi -e ./pi-sandbox-dockerBuild the sandbox image
The image is not built during install. Build it once:
/sandbox-buildThis builds pi-sandbox:debian-slim from the bundled image/Dockerfile
(bash, coreutils, git, curl, Python 3 + pip, Node.js + npm, build-essential,
jq, ripgrep, and friends).
Check readiness:
/sandbox-statusThis reports whether the Docker CLI exists, the daemon is reachable, and the image is present, with remediation steps if something is missing.
The sandbox_run tool
The model calls it to execute code in the sandbox. Two modes (exactly one):
Shell mode — pipelines, redirects, &&, etc.:
sandbox_run({ command: "pytest -q && python3 /workspace/check.py" })Argv mode — no shell interpretation:
sandbox_run({ executable: "python3", args: ["/workspace/check.py"] })Parameters
| Parameter | Type | Description |
| ----------- | ------ | ------------- |
| command | string | Shell command (mutually exclusive with executable) |
| executable | string | Executable to run without a shell |
| args | string[] | Arguments for the executable (argv mode) |
| setup | string[] | Privileged shell commands run as root before the main command (e.g. package install) |
| network | boolean | Enable network for THIS run only — requires human approval. Default false. |
| timeout_seconds | number | Timeout in seconds (default 120, max 3600) |
| artifacts | string[] | Relative paths under /output to export to the host |
Filesystem model
HOST PROJECT ──(read-only)──▶ /workspace
/output (writable, ephemeral export boundary)
/tmp (writable, ephemeral)/workspaceis your project, mounted read-only. The container can never modify it./outputis where code writes files the user/model explicitly wants out.- Export them with
artifacts: ["report.json", "coverage/index.html"]. Only files under/outputare exported; absolute paths,..,/workspace, and/tmpare rejected.
Network policy
- Off by default. If
networkis omitted orfalse, the run gets--network noneand no permission is asked. - If
network: true, Pi prompts the human for that individual run with Deny / Allow-once. There is no "always allow" and no session-wide grant. - No UI means no network. If
ctx.hasUIis false and network is requested, the run fails closed and no container is created.
Security model
- Fresh container per run, removed afterwards (including failure/timeout/cancel).
- Drops dangerous Linux capabilities (
SYS_ADMIN,SYS_PTRACE,NET_ADMIN,NET_RAW,MKNOD, and others) while keeping the ones package installation needs;--security-opt no-new-privileges; PID/memory/CPU limits. - No
--privileged. No Docker socket exposed. No host env, keys, SSH agents, or home directories leaked into the container. - Normal code runs as the unprivileged
sandboxuser. Root is used only for thesetupphase (privileged package installation). - The project mount uses
:ro,zso it works on hosts withselinux-enabled=true(Docker marks the volume shared). - This is not an absolute security boundary. Docker shares the host kernel; a determined attacker who achieves container escape could reach the host. For strong isolation use a micro-VM (see Pi's Gondolin extension) or a dedicated sandbox runtime.
Development & testing
npm install # installs typebox + pi types for typecheck
npm run check # no build step (jiti runs TS directly)
npm test # unit tests (no Docker required)Integration tests require Docker and the built image; run them when Docker is available.
Publishing
The package uses pi.extensions and lists @earendil-works/pi-coding-agent
and typebox as peer dependencies (Pi provides them at runtime).
Name: the npm names
pi-sandboxandpi-docker-sandboxare already taken. This package is published aspi-sandbox-docker.
Manual publish
npm adduser # once, if not already authenticated
npm pack # inspect the tarball
npm publish --access publicThen install it in Pi:
pi install npm:<your-package-name>CI publish (recommended, no manual token)
A .github/workflows/publish.yml is included. It publishes via OIDC trusted
publishing when you push a vX.Y.Z tag. To enable it:
- Push this repo to GitHub.
- Create an
npm-publishenvironment on the repo. - Add the npm OIDC publish-access trust relationship for that environment (npm docs: "Publishing with provenance and publish access / OIDC").
- Bump + tag with
npm version patch && git push --tags.
Fixtures: the repository.url placeholder in package.json must be set to
the real repo before publishing.
License
MIT. See LICENSE.
