pi-sandbox-docker

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.

Packages

Package details

extension

Install pi-sandbox-docker from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-sandbox-docker
Package
pi-sandbox-docker
Version
0.1.0
Published
Aug 10, 2026
Downloads
154/mo · 17/wk
Author
kiy-k
License
MIT
Types
extension
Size
50.4 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

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 ps works)
  • The sandbox image (build with /sandbox-build)

Installation

pi install npm:pi-sandbox-docker

For local development:

# from this repo
pi -e ./pi-sandbox-docker

Build the sandbox image

The image is not built during install. Build it once:

/sandbox-build

This 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-status

This 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)
  • /workspace is your project, mounted read-only. The container can never modify it.
  • /output is where code writes files the user/model explicitly wants out.
  • Export them with artifacts: ["report.json", "coverage/index.html"]. Only files under /output are exported; absolute paths, .., /workspace, and /tmp are rejected.

Network policy

  • Off by default. If network is omitted or false, the run gets --network none and 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.hasUI is 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 sandbox user. Root is used only for the setup phase (privileged package installation).
  • The project mount uses :ro,z so it works on hosts with selinux-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-sandbox and pi-docker-sandbox are already taken. This package is published as pi-sandbox-docker.

Manual publish

npm adduser            # once, if not already authenticated
npm pack               # inspect the tarball
npm publish --access public

Then 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:

  1. Push this repo to GitHub.
  2. Create an npm-publish environment on the repo.
  3. Add the npm OIDC publish-access trust relationship for that environment (npm docs: "Publishing with provenance and publish access / OIDC").
  4. 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.