pi-playpen

Global, multi-project OS sandboxing for the pi coding agent

Packages

Package details

extension

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

$ pi install npm:pi-playpen
Package
pi-playpen
Version
0.1.1
Published
Jul 11, 2026
Downloads
296/mo · 296/wk
Author
moreheadm
License
MIT
Types
extension
Size
123.6 KB
Dependencies
4 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

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

README

pi-playpen

pi-playpen is a global, multi-project sandbox extension for pi. It uses @anthropic-ai/sandbox-runtime for OS-level Bash isolation and replaces Pi's filesystem tools with policy-aware equivalents.

There are no per-command approval dialogs. A sandbox exposes only configured projects plus a read-only system runtime needed to launch commands. Standard OS paths and common Homebrew, MacPorts, Linuxbrew, Nix, and /opt installations are available to Bash automatically. User-managed toolchains elsewhere must be named explicitly in trusted global configuration. If an operation is not exposed, Pi receives a denial and the user must change trusted configuration.

Status

This is an initial implementation for macOS and Linux. Linux needs bwrap, socat, and rg; macOS needs rg. Windows is intentionally deferred.

Install

pi install /path/to/pi-playpen
# or, once published:
pi install npm:pi-playpen

Install dependencies when running from a checkout:

npm install
npm run verify

Global configuration

Create ~/.pi/agent/extensions/pi-playpen/config.jsonc; see examples/global-config.jsonc.

{
  "version": 1,
  "enabled": true,
  "defaultSandbox": "work",
  "sandboxes": {
    "work": {
      "projects": [
        { "root": "~/Programming/app", "access": "read-write" },
        { "root": "~/Programming/shared", "access": "read-only" },
        {
          "root": "~/.pi",
          "access": "read-write",
          "denyRead": ["agent/auth.json"],
          "denyWrite": ["agent/auth.json"],
        },
      ],
      "sandboxRuntime": {
        "network": {
          "allowedDomains": [],
          "deniedDomains": [],
        },
        "filesystem": {
          // Extra read-only user toolchains needed by Bash outside projects.
          // Prefer narrow paths such as ~/.nvm/versions or ~/.cargo/bin.
          "allowRead": [],
          // Additional host restrictions; Snowglobe adds its own root/project rules.
          "denyRead": [],
          // Runtime and direct-tool writes; these may be outside project roots
          // (for example, /tmp).
          "allowWrite": [],
          "denyWrite": [],
        },
      },
      "customTools": {
        "default": "block",
        "allow": ["web_search", "fetch_content", "get_search_content"],
      },
    },
  },
}

Snowglobe is enabled independently of Pi's launch directory. The active sandbox is selected by, in order:

  1. --playpen <name>;
  2. defaultSandbox;
  3. the sole configured sandbox.

A missing default with multiple sandboxes is a locked failure, never a cwd-based guess. Sandboxes have no working directory; relative paths use Pi's current directory and are denied unless that directory is inside an accessible project. Projects may overlap, including nested project roots; the most specific matching root supplies the direct-tool policy.

Set trusted global enabled: false to disable Snowglobe. A project configuration cannot disable or widen it. Project allowRead/allowWrite roots must remain inside that project's root. sandboxRuntime.filesystem.disabled is rejected, runtime allow entries must be literal paths, and runtime write allowances are shared with direct write/edit tools. Unknown configuration properties are errors rather than silently ignored typos.

Project configuration

A member project can narrow its policy in .pi/extensions/pi-playpen/config.jsonc:

{
  "version": 1,
  "bySandbox": {
    "work": {
      "ignoreFiles": [".cursorignore", ".claudeignore"],
      "denyRead": ["secrets/**"],
      "allowWrite": ["src", "test", "docs"],
      "denyWrite": ["secrets/**"],
      "deniedDomains": ["uploads.example.com"],
    },
  },
}

Local path entries must be project-relative and cannot use .., ~, or absolute paths. Local allowRead/allowWrite entries must be inside the global allowance. Denies and ignore-file names are additive. Local configuration cannot add projects, network allowlist entries, host filesystem access, runtime weakening flags, or custom-tool permissions.

Ignore files hide matching paths from read, write, edit, ls, find, and grep; their own files are also inaccessible. Existing ignored paths are compiled into the Bash runtime's read/write restrictions. The project Snowglobe configuration and global Snowglobe configuration are write-protected.

Commands

  • /playpen — active sandbox, projects, and custom-tool policy.
  • /playpen-doctor — state and basic runtime diagnostics.
  • /playpen-use <name> — switch sandboxes. This resets and reinitializes Sandbox Runtime; a failed switch attempts to restore the prior sandbox and locks tools if restoration fails.
  • /playpen-add [folder] — choose a sandbox and add the folder as a read-write project, then reinitialize that sandbox. With no folder, choose the current directory or enter another folder in the dialogue; relative folders are resolved from the current directory.
  • /playpen-on — enable Playpen for the session, restoring the previously active sandbox or selecting one from configuration.
  • /playpen-off — disable Playpen enforcement for the session and restore Pi's normal tools.

The on/off commands are session-scoped and do not change global configuration. Turning Playpen off intentionally removes its sandbox boundary until /playpen-on is used.

Security model

  • bash and ! commands run through Sandbox Runtime.
  • Pi's seven built-ins (read, write, edit, ls, find, grep, bash) are replaced or guarded.
  • Managed tool calls, runtime initialization, switching, and shutdown are serialized by Snowglobe's execution gate. This prevents Snowglobe-internal policy races; it cannot prevent an unrelated host process from swapping a path between a direct tool's validation and use.
  • Reads and writes resolve canonical paths and must remain inside a configured project.
  • Symlinks are not traversed by directory walkers.
  • Custom model-invoked tools are allowed by default. Set customTools.default to "block" in trusted global configuration when arbitrary extensions or MCP tools must be denied, then add reviewed tools to customTools.allow. Snowglobe also verifies that its seven managed tool names still resolve to its own registrations and blocks calls after a name collision.
  • Other installed Pi extensions are trusted host code and remain outside Snowglobe's security boundary.

Sandbox Runtime supplies OS protection for subprocesses. Snowglobe emits a root read deny, re-allows a documented read-only system runtime and configured project paths, and compiles project deny/ignore rules into runtime restrictions. Explicit sandboxRuntime.filesystem.allowRead roots are shared with direct read, ls, find, and grep calls. For example, allowing the installed pi package directory makes its documentation readable through both Bash and direct tools. Automatically added system/runtime paths remain Bash-only, so a direct ls /usr/bin may still be denied even though Bash can execute /usr/bin/git. Runtime allowRead entries never grant direct writes; runtime allowWrite entries are shared with direct write/edit tools. On Linux, Bubblewrap cannot enforce arbitrary future glob matches: Snowglobe expands current matches, and a literal directory or trailing /** rule protects the whole subtree. Use literal directory/file deny paths for hard Bash restrictions on paths that do not yet exist. A read-masked directory is backed by an ephemeral tmpfs, so a write inside it may report success but cannot modify the hidden host directory.

Development

npm run check
npm test
npm run verify

The project prefers jj for source control; ensure it is installed before committing changes.