Files
paseo/goals/research/pi-sandbox-requirements.md
T

38 KiB

Pi sandbox requirements

Research scope: Pi 0.84.2, Paseo's current direct Pi provider, and pi-mcp-adapter 2.27.0. This note defines the smallest useful contract for a real pi --mode rpc process under gVisor. It covers the host-installed daemon and the official Paseo Docker image. It does not choose a sandbox launcher or implement the contract.

Findings

Process and image contents

Paseo spawns Pi as a JSONL child process with piped stdin, stdout, and stderr. A normal launch has this shape:

pi --mode rpc \
  --model <provider/model> \
  --thinking <level> \
  [--session <absolute-jsonl-path> | --no-session] \
  [--mcp-config <generated-file>] \
  --extension <generated-paseo-extension>

Paseo sets the child cwd to the agent cwd and forwards the daemon environment plus provider overrides and PASEO_AGENT_ID and PASEO_AGENT_CWD. Pi adds AI_AGENT=pi and PI_CODING_AGENT=true for its descendants. Commands launched by Pi also receive PI_SESSION_ID, PI_SESSION_FILE, PI_PROVIDER, PI_MODEL, and PI_REASONING_LEVEL.1 2 3

A minimum npm-based image needs:

Content Requirement
Linux userspace A glibc image such as Debian bookworm slim. The official Pi container example uses node:24-bookworm-slim.4
Node Node >=22.19.0, matching Pi's package engine. Copying only the pi script without Node and its package tree will not work.5
Pi The complete installed @earendil-works/pi-coding-agent package and its production dependency tree, or an official standalone Pi binary plus its adjacent assets. Pin the version in the sandbox image.
TLS ca-certificates and working DNS. Provider requests use HTTPS unless the selected model has a local or custom HTTP base URL.
Shell bash. Pi's built-in bash tool launches a configured shell, and the official Pi container installs bash.4 6
Search helpers Preinstall rg and fd/fdfind. Pi's grep and find tools otherwise download them from GitHub into <agent-dir>/bin, adding writable state and broad GitHub egress.7
Project tooling git is part of Pi's official container recommendation and is needed for normal coding-agent work. Add only the compilers, package managers, SSH client, and project CLIs that the session is allowed to invoke. Pi does not provide them.4
MCP adapter Install and pin pi-mcp-adapter plus its production dependencies if the session must receive Paseo tools over MCP. Pi has no built-in MCP client.8 9

pi-mcp-adapter ships TypeScript source and Pi loads it through its extension loader. Copy its complete installed package tree, including the platform package for @napi-rs/keyring. Paseo disables both adapter OAuth and automatic auth for its own local HTTP server, so the local Paseo MCP path does not need an OS keyring. Other MCP servers that use OAuth or bearerTokenStore need a functioning OS credential store; the adapter fails closed when it cannot access one. Headless Linux usually needs a Secret Service session, and its keyring recovery path also expects keyctl and node.10

The image should set these defaults:

HOME=/home/sandbox
PI_CODING_AGENT_DIR=/state/pi-agent
PI_CODING_AGENT_SESSION_DIR=/state/pi-sessions
PI_OFFLINE=1
PATH=<fixed image paths plus explicitly allowed project tool paths>
TMPDIR=/tmp

PI_OFFLINE=1 removes Pi's startup catalog/update traffic. It does not block model calls. Pi RPC otherwise starts a background model-catalog refresh. Disabling this traffic makes a network allowlist predictable; bake the required model catalog or project models.json into the state projection.11 12

Files Pi reads

Pi resolves global state from PI_CODING_AGENT_DIR, defaulting to ~/.pi/agent. Pointing it at a dedicated sandbox directory avoids any need to mount the daemon user's home.13

Path Access Required for the minimum contract Treatment
<agent-dir>/auth.json Read and sometimes write One credential source, unless credentials arrive through environment variables or --api-key Project only the selected provider credential. Keep writable when it contains OAuth because Pi refreshes and rewrites tokens under a file lock. API-key-only sessions can use a secret environment variable and omit the file.14 15
<agent-dir>/settings.json Read A small generated file Copy only settings that change runtime behavior: proxy, custom session directory, model defaults not supplied by Paseo, and the pinned MCP adapter package/resource declaration. Avoid copying unrelated extension/package declarations.
<agent-dir>/models.json Read Only for custom providers/models Copy read-only when the selected model depends on it. It may contain inline keys or command-backed key resolution, so treat it as a credential-bearing file.14
<agent-dir>/models-store.json Read/write cache Only for a dynamic catalog or offline cached model absent from the built-in catalog Seed narrowly and permit cache writes, or omit when the selected model is built in. Pi stores refreshed catalogs here.14 16
<agent-dir>/trust.json Read Only if project .pi settings/resources must load in non-interactive RPC mode Copy a narrow decision for the mounted cwd, or set a generated global defaultProjectTrust. Do not inherit trust decisions for unrelated host paths.17
<agent-dir>/mcp.json Read by Paseo before launch and by the adapter Optional user-global MCP settings Paseo merges it into the generated per-session MCP config. Copy only MCP entries approved for this sandbox.
<agent-dir>/mcp-cache.json Read/write Adapter metadata optimization, direct-tool bootstrap Persist in sandbox state if direct MCP tools or cached metadata matter. It contains tool metadata and config hashes, not the Paseo bearer token.18
<agent-dir>/mcp-npx-cache.json and agent-plugin-data/ Read/write Only for configured npx stdio servers or Agent Plugins Persist only when those features are enabled. Their executables and data expand the sandbox contract.
<agent-dir>/extensions, skills, prompts, themes, npm, git Read; package manager may write Only resources selected for the session Bake pinned resources into the image read-only or copy an allowlisted subset. Pi extensions execute arbitrary code. Do not project the daemon user's complete package trees.19 20
<agent-dir>/AGENTS.md, SYSTEM.md, APPEND_SYSTEM.md Read Optional global prompt behavior Copy only when Paseo promises parity with the user's Pi configuration.
<cwd>/.pi/*, <cwd>/.mcp.json, <cwd>/.agents/skills, context files Read, and project tools may write under cwd Depends on project trust These arrive through the workspace mount. RPC ignores project resources unless trust resolves to yes; ask cannot open a trust prompt in RPC mode.17

Pi also walks from cwd toward the filesystem root for AGENTS.md, AGENTS.override.md, or CLAUDE.md. A workspace-only mount will omit instructions stored above the workspace. The mount planner must either project the approved parent context files at their original paths or declare that sandboxed sessions load workspace-contained context only.8

State that must persist

Persist these objects independently instead of persisting a synthetic home directory:

  1. Pi session JSONL. Normal Paseo Pi agents omit --no-session; Pi writes under <session-dir>/<encoded-cwd>/. Paseo stores the absolute JSONL path as nativeHandle, resumes with --session <path>, and scans the configured session directory for imports. The daemon and later sandboxes must see the same stable absolute path.21 22 23
  2. Mutable provider credentials. Pi writes refreshed OAuth tokens back to auth.json. API keys delivered by environment do not require persistent credential files.15
  3. Adapter metadata caches when direct tools or fast lazy discovery matter. They can be discarded at the cost of reconnecting and repopulating metadata.18
  4. Optional MCP secure-store state. Only non-Paseo MCP servers using OAuth or adapter bearer storage need this. OS credential-store state is outside <agent-dir> and cannot be preserved by copying files alone.10

Keep the rest ephemeral. Give each sandbox a private writable /tmp for Pi bash spill files, image materialization, generated tool output, and MCP output/resource files. The adapter documents that some oversized MCP text spill files are not automatically deleted, so destroy the sandbox temp filesystem after the session.24

Workspace and project mounts

Mount the agent cwd read-write at the same absolute path recorded by Paseo. Pi writes that cwd into the session header, Paseo uses it in persistence metadata, and resume launches use it as the process cwd. Path translation would break import filtering, parent context discovery, absolute links in history, and commands saved in the transcript.22 21

The minimum project mount set is:

  • the cwd tree, read-write;
  • the linked worktree's actual Git metadata, when .git is a file or points outside cwd;
  • any explicit external directories that the product chooses to grant;
  • the persistent session and minimal agent-state mounts described above;
  • the two generated Paseo launch files, read-only, at the paths passed on argv.

Do not follow workspace symlinks into arbitrary host paths by mounting their targets. Let missing external targets fail unless a separate policy grants them. For Git linked worktrees, resolve .git, commondir, and object alternates before launch. Mount only the required Git dirs. They need write access for normal branch, index, object, and ref operations; a read-only Git mount supports inspection only.

Paseo-generated extension and MCP config

For every Pi create or resume, Paseo creates two host temp directories:

  • paseo-pi-extension-*/paseo-integration.mjs, passed with --extension. It appends Paseo's per-agent and daemon system prompts and emits session-tree markers over Pi extension UI notifications.
  • paseo-pi-mcp-*/mcp.json, passed with --mcp-config when Paseo detects pi-mcp-adapter. It starts from <agent-dir>/mcp.json, overlays injected servers, writes mode 0600, and deletes the temp directory when the Pi session closes.25

The gVisor launcher must bind each file or its containing directory into the sandbox at the exact argv path. Do not share the daemon's /tmp. Mount these paths read-only. The generated MCP file contains the per-daemon-run Paseo bearer token in an Authorization header and must stay private to that sandbox. The generated extension can contain system-prompt text; change its creation mode to 0600 in the later implementation ticket.

Paseo detects adapter support by launching a scratch Pi RPC process for the same cwd and calling get_commands; it accepts an extension command named mcp with adapter source metadata. All Pi probes, including catalog fetches and this detection process, need the same image, state projection, cwd mounts, and sandbox policy as the real session. Otherwise the daemon can report a different provider capability than the session receives.26

For injected HTTP MCP servers, Paseo converts the entry to:

{
  "url": "http://<daemon-address>/mcp/agents?callerAgentId=<agent-id>",
  "headers": { "Authorization": "Bearer <per-daemon-run-token>" },
  "auth": false,
  "oauth": false
}

The adapter still reads standard user and project MCP layers around --mcp-config. The minimum Paseo contract should include only the injected paseo server. Supporting arbitrary user MCP config adds every referenced executable, cwd, environment secret, socket, remote host, package cache, and credential store to the contract.27 28

Local MCP connectivity

The current address generation assumes Pi shares the daemon network namespace. A daemon listening on 0.0.0.0 becomes 127.0.0.1; an IPv6 wildcard becomes ::1. A gVisor sandbox with its own network namespace resolves those addresses to itself, so current injected MCP URLs will fail.29

The network ticket must provide a sandbox-specific MCP route and pass that route into runtime MCP config generation. Acceptable shapes include:

  • a narrow host-gateway listener reachable only from the sandbox network;
  • a per-sandbox proxy that exposes only /mcp/agents and forwards to daemon loopback;
  • a Unix socket transport after both Paseo and the adapter support it for this server.

Do not expose the daemon's full 6767 listener to the sandbox network if a route-specific proxy can avoid it. Keep the per-daemon bearer token even on the private route. The token authorizes agent-scoped operations and rotates when the daemon restarts.30

Egress contract

A real turn needs these network capabilities:

Destination Required Policy
Configured DNS resolver Yes for hostname endpoints Allow DNS only to the sandbox resolver. Do not allow arbitrary port 53 destinations.
Selected model baseUrl Yes, unless the model is local Resolve the exact effective provider/model after Pi reads auth.json, models.json, and provider-scoped environment. Allow its host and port, normally HTTPS 443. Custom providers make a static global hostname list incomplete.14
Provider OAuth refresh/token endpoint Only for subscription OAuth Allow the selected provider's auth endpoints. A refreshed credential writes back to auth.json; read-only credentials will fail when tokens expire.14
Paseo Agent MCP route Yes when Paseo tools are enabled Allow the one private daemon/proxy address and path. It is local connectivity, not Internet egress.
Other configured HTTP MCP servers Only when explicitly enabled Add each resolved MCP URL and its OAuth metadata/token endpoints. Paseo's injected server disables OAuth and needs no discovery.28
Stdio MCP descendants Only when explicitly enabled No network by default, but the child server may need its own egress. Treat each command as a separate workload policy.
pi.dev No for RPC operation Set PI_OFFLINE=1. Pi's documented update and telemetry endpoints are not needed for model calls.11
GitHub API/releases No when rg and fd are baked in Pi downloads missing search helpers from GitHub. Preinstall them.7
npm and Git hosts No when packages and MCP servers are pinned in the image Disallow runtime package installation. npx -y MCP entries otherwise require registry and tarball egress and mutable npm caches.
Arbitrary destinations from bash or extensions Product decision A coding agent can call curl, package managers, or custom extension code. Provider-only egress requires network enforcement below the process, not prompt instructions.

Pass proxy variables only if the selected policy requires them. Pi supports HTTP_PROXY and HTTPS_PROXY, and a global httpProxy setting applies both. Ensure NO_PROXY covers the private Paseo MCP endpoint or route it through a proxy that can reach the daemon.3

Explicit minimum sandbox contract

A sandboxed Paseo Pi session is ready only when the launcher satisfies all of these conditions:

  1. Identity: run as a non-root uid/gid with no host capabilities, no daemon Docker socket, and a private process, IPC, temp, and mount namespace.
  2. Image: provide pinned Pi, Node >=22.19.0, production dependencies and assets, CA roots, bash, git, rg, and fd; add the pinned MCP adapter package when Paseo tools are enabled.
  3. Cwd: mount the exact agent cwd read-write at the same absolute path. Add only required external Git metadata and separately approved paths.
  4. Agent state: set PI_CODING_AGENT_DIR to a minimal host-visible state directory. Include only selected auth, settings, custom model files, and pinned resources. Do not mount $HOME, /home/paseo, ~/.pi, ~/.ssh, cloud config dirs, or the daemon's $PASEO_HOME wholesale.
  5. Sessions: set PI_CODING_AGENT_SESSION_DIR to a stable, host-visible, writable directory and preserve absolute session paths across launches. Scope it by daemon user or tenant; scope further by agent if cross-session import is not required.
  6. Credentials: prefer one selected-provider API key or a brokered secret. If Pi OAuth is required, give auth.json coordinated read-write persistence. Do not duplicate a mutable OAuth file into independent sandboxes without a refresh reconciliation design.
  7. Generated files: mount only the generated Paseo extension and MCP config read-only at their argv paths. Destroy them after the child exits.
  8. MCP: install the adapter, confirm its mcp command during a sandboxed probe, and give the sandbox a private route to /mcp/agents. Keep auth: false, oauth: false, and the per-run bearer header for the injected server.
  9. Network: allow the private Paseo MCP route, DNS to the configured resolver, the selected model endpoint, and any required provider token-refresh endpoints. Deny other egress by default. Set PI_OFFLINE=1 and bake helpers/packages.
  10. Lifecycle: sandbox create, resume, import hydration, catalog probes, and adapter probes. Kill the process tree and remove ephemeral temp state when a Pi session closes.
  11. Limits: cap pids, memory, CPU, disk, file descriptors, output, and wall time. Pi and extensions can spawn children; stdio MCP and bash tools add more processes.

Daemon topology implications

Host-installed daemon

The daemon can bind the host workspace and persistent Pi state paths directly into the sandbox. It still must translate daemon loopback MCP connectivity because sandbox loopback is separate. A host gateway or path-limited proxy should be created per sandbox. Keep host $HOME outside the mount table.

Host-installed Pi may come from npm, Bun's global package tree, a version manager, or a standalone binary. Do not bind the discovered host installation into the sandbox as the production design. Build a pinned runtime image so the process cannot traverse the host package manager's global tree and so native dependencies match the sandbox libc and architecture.

Official Docker Paseo daemon

The published daemon image runs as uid/gid 1000:1000, sets HOME=/home/paseo, stores Paseo and agent state under /home/paseo, mounts workspaces at /workspace, and intentionally omits every agent CLI.31

A sibling gVisor sandbox cannot assume that /workspace inside the daemon container names a host bind source. The daemon-side launcher needs mount provenance: named volume identity or the original bind source plus destination. Bind the same workspace volume into the sandbox at the same in-container absolute path. Apply the same rule to persistent Pi sessions and minimal agent state.

Do not pass the official /home/paseo volume through to the sandbox. It contains $PASEO_HOME, daemon identity and config, logs, all provider credentials, and unrelated agent state. Create separate Pi credential/session volumes or copy approved files into sandbox-specific state. The official base image's Node and utilities also do not satisfy a sibling sandbox; the sandbox image owns its own runtime.

The Docker deployment ticket must also decide who owns gVisor container creation. Mounting a Docker/containerd socket into the Paseo daemon grants host-level control and conflicts with the sandbox goal. Prefer a narrow launcher service or runtime API that validates image, mounts, network, limits, and argv.

What to copy, project, or persist

Item Strategy
Pi, Node, MCP adapter, CA roots, bash, git, rg, fd Bake read-only into the image.
Workspace and required Git metadata Bind explicit paths, cwd read-write, at stable absolute destinations.
Paseo integration extension and merged MCP config Bind two generated files read-only for one process.
API keys Inject as per-process secrets or provider env. Avoid broad inherited daemon env.
Pi OAuth auth.json Use one coordinated writable credential store or a broker. Persist and protect it independently.
Pi JSONL sessions Persist in a dedicated writable volume visible to daemon and future sandboxes.
Minimal settings, custom model catalog, trust decision Generate or copy an allowlisted subset. Prefer read-only unless Pi must refresh its model store.
MCP metadata/npx caches Persist only for enabled adapter features. They are rebuildable.
User extensions/packages/skills/prompts Bake pinned approved resources or bind explicit read-only trees. Omit ambient resources by default.
/tmp, bash spill output, materialized images/MCP resources Private writable ephemeral filesystem, destroyed on teardown.
$HOME, /home/paseo, $PASEO_HOME, .ssh, cloud credential directories Never mount wholesale. Grant individual files, sockets, or brokered credentials only.

Remaining uncertainties

  1. MCP adapter ownership and version. Paseo detects an ambient pi-mcp-adapter but does not install or pin it. The sandbox image ticket must choose a supported version and define its upgrade compatibility with Pi and Paseo.
  2. MCP route design. Current loopback URL generation cannot cross a gVisor network namespace. The network ticket must select a route-specific proxy, gateway address, or future Unix socket transport and define token exposure.
  3. OAuth concurrency. Pi can rewrite auth.json during token refresh. The credential ticket must define locking and reconciliation across simultaneous sandboxes and daemon-side provider probes.
  4. Project trust parity. Decide whether sandboxed RPC sessions load project .pi resources and ancestor context files. A generated defaultProjectTrust: "never" is safer but changes current trusted-project behavior.
  5. User extensions and MCP servers. The minimum contract includes the Paseo extension and Paseo MCP only. Supporting ambient user Pi packages or arbitrary MCP config changes image contents, mounts, egress, subprocess policy, and credential requirements.
  6. Git worktree mounts. The mount ticket must specify how it resolves and grants linked-worktree gitdir, commondir, alternates, submodules, and LFS without exposing unrelated repositories.
  7. Official Docker launcher authority. The deployment ticket must choose a launcher architecture that avoids granting the daemon an unrestricted container runtime socket.
  8. Path namespace and import UX. Stable absolute paths simplify resume and import. If the product requires path remapping, Paseo needs an explicit host-to-sandbox path identity layer before sandboxing ships.
  9. OS credential store in gVisor. Adapter OAuth for non-Paseo MCP servers depends on a keyring service and native binding. Test whether the chosen gVisor image and launcher can provide a scoped Secret Service session; otherwise disable that feature or broker it.
  10. Provider egress inventory. Pi supports many built-in and custom providers. Generate policy from the effective selected model and credential type at launch; a hand-maintained host list will drift.

Implications for follow-up tickets

  • Mount ticket: introduce explicit mount descriptors for cwd, external Git metadata, persistent sessions, minimal agent state, generated launch files, and optional approved resources. Record Docker volume provenance.
  • Security ticket: isolate uid, namespaces, temp, process tree, secrets, and resource limits; reject broad home, runtime-socket, and daemon-state mounts; make the generated extension mode 0600.
  • Network ticket: replace loopback MCP assumptions with a private sandbox route, generate provider-specific egress, enforce DNS policy, set offline mode, and keep package/helper downloads out of runtime.
  • Credential ticket: select env injection, a credential broker, or a coordinated writable Pi auth store. Cover OAuth refresh and adapter keyring behavior.
  • Runtime image ticket: pin Pi and adapter versions, bake their complete dependencies and assets, preinstall rg/fd, and test create, prompt, tool call, MCP call, close, resume, and import under both daemon topologies.

Sources

Paseo repository sources use paths from this checkout. Upstream links point to the researched versions.


  1. packages/server/src/server/agent/providers/pi/runtime.ts:75-139 and cli-runtime.ts:60-86. ↩︎

  2. packages/server/src/server/agent/agent-manager.ts:4767-4788 and paseo-env.ts:1-43. ↩︎

  3. Pi 0.84.2, Environment Variables and README.md, environment table. ↩︎

  4. Pi 0.84.2, Containerization, Plain Docker. ↩︎

  5. Pi 0.84.2, packages/coding-agent/package.json, engines.node and production dependencies. ↩︎

  6. Pi 0.84.2, src/core/tools/bash.ts:83-106. ↩︎

  7. Pi 0.84.2, src/utils/tools-manager.ts:15-116, 231-319 and grep.ts:174-180, find.ts:222-232. ↩︎

  8. Pi 0.84.2, README.md, Philosophy and MCP. ↩︎

  9. pi-mcp-adapter 2.27.0, package.json, including pi.extensions, dependencies, peer dependencies, and Node engine. ↩︎

  10. pi-mcp-adapter 2.27.0, Remote/headless OAuth and mcp-auth.ts:1-18, 274-391. ↩︎

  11. Pi 0.84.2, README.md, Telemetry and update checks. ↩︎

  12. Pi 0.84.2, src/main.ts:926-940. ↩︎

  13. Pi 0.84.2, src/config.ts:494-565. ↩︎

  14. Pi 0.84.2, Providers, including credential resolution order, auth file writes, cloud credentials, and custom providers. ↩︎

  15. Pi 0.84.2, src/core/auth-storage.ts:47-188. ↩︎

  16. Pi 0.84.2, src/core/model-runtime.ts:171-212 and models-store.ts:49-53. ↩︎

  17. Pi 0.84.2, Settings, Project Trust. ↩︎

  18. pi-mcp-adapter 2.27.0, metadata-cache.ts:39-79 and Direct Tools. ↩︎

  19. Pi 0.84.2, Settings, Resources and Extensions, Security. ↩︎

  20. Pi 0.84.2, Pi Packages, package sources and dependencies. ↩︎

  21. Pi 0.84.2, README.md, Sessions and session-manager.ts:474-487. ↩︎

  22. packages/server/src/server/agent/providers/pi/agent.ts:467-527, 2462-2523. ↩︎

  23. packages/server/src/server/agent/providers/pi/session-descriptor.ts:72-145. ↩︎

  24. pi-mcp-adapter 2.27.0, Output Guard and mcp-output-guard.ts:358-363. ↩︎

  25. packages/server/src/server/agent/providers/pi/agent.ts:564-736. ↩︎

  26. packages/server/src/server/agent/providers/pi/agent.ts:750-758, 2623-2653. ↩︎

  27. packages/server/src/server/agent/providers/pi/agent.ts:239-252, 512-527 and runtime-mcp-config.ts:24-59. ↩︎

  28. pi-mcp-adapter 2.27.0, Config and precedence and config.ts:394-451. ↩︎

  29. packages/server/src/server/bootstrap.ts:243-260. ↩︎

  30. packages/server/src/server/bootstrap.ts:631-638, auth.ts:135-181, and runtime-mcp-config.ts:24-59. ↩︎

  31. docs/docker.md and docker/base/Dockerfile. ↩︎