Agent Sandbox
PizzaPi can enforce OS-level sandboxing around every tool call the agent makes — bash commands, file reads/writes, and MCP server interactions. This prevents prompt injections or model hallucinations from accessing sensitive files, exfiltrating data, or damaging your system.
The sandbox is powered by Anthropic’s sandbox-runtime, which uses macOS sandbox-exec and Linux bubblewrap + seccomp to enforce restrictions at the kernel level.
Sandbox Modes
Section titled “Sandbox Modes”PizzaPi provides three preset modes. The default is basic.
| Mode | Filesystem | Network | Summary |
|---|---|---|---|
none | Unrestricted | Unrestricted | Sandbox disabled. All tools run directly on the host. |
basic | Protected ✅ | Unrestricted | Blocks reads/writes of sensitive dotfiles. Network is unrestricted. (default) |
full | Protected ✅ | Deny-all by default ✅ | Full enforcement. Network is blocked unless you explicitly allow domains. |
Configure the mode in ~/.pizzapi/config.json:
{ "sandbox": { "mode": "full" }}Or via CLI flag:
pizza --sandbox fullpizza --sandbox basicpizza --sandbox noneOr environment variable:
PIZZAPI_SANDBOX=full pizzaPIZZAPI_NO_SANDBOX=1 pizza # shorthand for noneWhat basic protects
Section titled “What basic protects”The basic preset blocks the most common exfiltration targets:
| Category | Protected paths |
|---|---|
| Read blocked | ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud, ~/.docker/config.json, browser data dirs |
| Write blocked | .env, .env.local, ~/.ssh |
| Write allowed | Current working directory (.), /tmp |
Network access is unrestricted in basic mode — the agent can connect to any host.
What full adds
Section titled “What full adds”The full preset adds network enforcement on top of basic:
- Network deny-all by default — all outbound connections are blocked unless you add
allowedDomains. - Same filesystem protections as
basic.
To open specific domains in full mode, add overrides:
{ "sandbox": { "mode": "full", "network": { "allowedDomains": [ "*.github.com", "api.anthropic.com", "registry.npmjs.org" ] } }}Overrides
Section titled “Overrides”Any field you add under sandbox is a srt-native override merged on top of the preset. The format matches ~/.srt-settings.json exactly.
Filesystem overrides
Section titled “Filesystem overrides”{ "sandbox": { "mode": "basic", "filesystem": { "denyRead": ["~/.kube", "~/.terraform"], "allowWrite": [".", "/tmp", "~/my-project"], "denyWrite": [".env.production"] } }}denyRead— additional paths to block from reading (merged with preset)allowWrite— paths the agent may write to (replaces preset when specified)denyWrite— additional paths to block from writing (merged with preset)
Network overrides
Section titled “Network overrides”{ "sandbox": { "mode": "full", "network": { "allowedDomains": ["*.github.com", "api.example.com"], "deniedDomains": ["ads.example.com"], "allowLocalBinding": true, "allowUnixSockets": ["/var/run/docker.sock"] } }}allowedDomains— domains the agent can reach (allow-only;[]= deny all)deniedDomains— domains to explicitly blockallowLocalBinding— allow the process to bind local ports (default:true)allowUnixSockets— Unix socket paths to allow (blocked by default)allowAllUnixSockets— disable Unix socket blocking entirely
Other srt options
Section titled “Other srt options”These pass through directly to sandbox-runtime:
{ "sandbox": { "mode": "basic", "mandatoryDenySearchDepth": 5, "enableWeakerNetworkIsolation": true, "allowPty": true, "ignoreViolations": { "jest": ["/tmp"] } }}See the srt documentation for details on each option.
SSH Agent Detection
Section titled “SSH Agent Detection”The sandbox automatically detects your SSH agent socket via $SSH_AUTH_SOCK and adds it to the Unix socket allowlist. This means git push, git pull, and other SSH operations work transparently inside the sandbox.
Merge Semantics (project vs global config)
Section titled “Merge Semantics (project vs global config)”When both global (~/.pizzapi/config.json) and project (.pizzapi/config.json) configs exist:
| Field | Merge behavior |
|---|---|
mode | Project can only escalate (none → basic → full), never weaken |
filesystem.denyRead | Union — project adds more denials, never removes global ones |
filesystem.denyWrite | Union — same |
filesystem.allowWrite | Intersection — project can only narrow, never widen |
network.allowedDomains | Intersection — project can only narrow |
network.deniedDomains | Union — project adds more denials |
Platform Support
Section titled “Platform Support”| Platform | Enforcement | Status |
|---|---|---|
| macOS | sandbox-exec (built-in) | ✅ Fully supported |
| Linux | bubblewrap + seccomp | ✅ Supported — requires bubblewrap and socat |
| Windows | — | ⚠️ Graceful degradation (sandbox disabled) |
On Linux, install dependencies:
sudo apt install bubblewrap socatOn unsupported platforms, the sandbox initializes but is inactive — all tools execute without restrictions.
Violation Logging
Section titled “Violation Logging”When the sandbox blocks an operation, it records a violation event:
- Timestamp
- Operation (
read/write) - Target path
- Reason for denial
Violations are stored in a ring buffer (100 entries max). In sessions connected to the PizzaPi relay, violations are forwarded as sandbox:violation events visible in the web UI.
Use the /sandbox slash command in a session to view status and recent violations:
/sandbox # status overview/sandbox violations # full violation log/sandbox config # show resolved configRuntime Inspection & Configuration API
Section titled “Runtime Inspection & Configuration API”PizzaPi exposes REST endpoints and a slash command to inspect and update sandbox configuration at runtime — no restart required.
GET /api/runners/{runnerId}/sandbox-status
Section titled “GET /api/runners/{runnerId}/sandbox-status”Returns the current sandbox state for a runner. Requires an authenticated session owned by the runner’s user.
Response:
{ "mode": "basic", "active": true, "platform": "darwin", "violations": 3, "recentViolations": [ { "timestamp": "2026-03-29T14:22:01.000Z", "operation": "read", "target": "~/.ssh/id_ed25519", "reason": "Path in denyRead" } ], "config": { "mode": "basic", "filesystem": { "..." : "..." } }}| Field | Description |
|---|---|
mode | Active sandbox mode (none, basic, full) |
active | Whether enforcement is actually running (may be false on unsupported platforms) |
platform | Runner’s OS (darwin, linux, win32) |
violations | Total violation count since runner start |
recentViolations | Last 20 violations (newest first) |
config | Fully resolved sandbox config (global + project merged) |
PUT /api/runners/{runnerId}/sandbox-config
Section titled “PUT /api/runners/{runnerId}/sandbox-config”Update sandbox configuration at runtime without restarting the runner. The body is deep-merged with the existing global config in ~/.pizzapi/config.json.
Request body:
{ "mode": "full", "network": { "allowedDomains": ["*.github.com", "api.anthropic.com"] }}Response:
{ "saved": true, "resolvedConfig": { "..." : "..." }, "message": "Changes will apply on next session start."}- Send
nullfor a key to remove it from the config (e.g., clearing stale network rules when downgrading fromfulltobasic). - Nested objects are shallow-merged; arrays and scalars are replaced.
- Validation rejects invalid modes and non-string array entries with a
400status.
Violation events in the web UI
Section titled “Violation events in the web UI”When a runner is connected to the PizzaPi relay, every sandbox:violation event is forwarded in real time. In the web UI:
- Violations appear as warning badges in the session timeline.
- The runner detail panel shows a live violation counter and a filterable violation log.
- Clicking a violation expands it to show the full operation, target path, and denial reason.
/sandbox slash command
Section titled “/sandbox slash command”The /sandbox command is available inside any active session:
| Command | Description |
|---|---|
/sandbox | Status overview — mode, active state, platform, violation count |
/sandbox violations | Full violation log (last 100 entries) |
/sandbox config | Show the fully resolved config (global + project merged) |
Runner Workspace Roots
Section titled “Runner Workspace Roots”The PIZZAPI_WORKSPACE_ROOTS environment variable restricts which working directories the runner daemon will accept when spawning new sessions. Any spawn request whose cwd is outside the allowed roots is rejected before a session starts.
This is useful for shared or team runners that should only work within specific project directories — e.g., a CI runner locked to /srv/projects or a team Mac Mini restricted to a few repos.
Setting workspace roots
Section titled “Setting workspace roots”Set a comma-separated list of allowed directories:
export PIZZAPI_WORKSPACE_ROOTS="/Users/jordan/Projects,/tmp/scratch"Aliases (checked in priority order):
| Variable | Notes |
|---|---|
PIZZAPI_WORKSPACE_ROOTS | Preferred — comma-separated list |
PIZZAPI_WORKSPACE_ROOT | Convenience alias for a single root |
PIZZAPI_RUNNER_ROOTS | Legacy alias (back-compat) |
If PIZZAPI_WORKSPACE_ROOTS is set, the other variables are ignored. If none are set, the runner is unscoped and accepts any cwd.
Behavior
Section titled “Behavior”- Paths are normalized — trailing slashes are stripped, symlinks are resolved.
- A session’s
cwdmust be equal to or a subdirectory of at least one root. - Path traversal attacks (
/../) are neutralized viarealpathSyncbefore comparison. - On Windows, comparisons are case-insensitive.
- The filesystem root (
/) as a root allows everything (equivalent to unscoped).
LaunchAgent integration
Section titled “LaunchAgent integration”To persist workspace roots for the runner daemon, add them to the LaunchAgent plist:
<key>EnvironmentVariables</key><dict> <key>PIZZAPI_WORKSPACE_ROOTS</key> <string>/Users/jordan/Projects,/Users/jordan/Clients</string></dict>After editing the plist, reload the LaunchAgent:
launchctl unload ~/Library/LaunchAgents/com.pizzapi.runner.plistlaunchctl load ~/Library/LaunchAgents/com.pizzapi.runner.plistRelationship to sandbox filesystem restrictions
Section titled “Relationship to sandbox filesystem restrictions”Workspace roots and sandbox restrictions are complementary:
| Mechanism | Scope | Enforces |
|---|---|---|
| Workspace roots | Runner level — controls which directories the daemon will start sessions in | Where sessions can be created |
| Sandbox filesystem | Session level — controls what the agent can read/write during execution | What the agent can access at runtime |
A session must pass both checks: the runner must allow the cwd (workspace roots), and then the sandbox must allow each individual file operation (filesystem rules). Neither replaces the other.
Troubleshooting
Section titled “Troubleshooting””Sandbox blocked: Read denied”
Section titled “”Sandbox blocked: Read denied””The agent tried to read a file in denyRead. To allow it, remove the path from your config’s denyRead, or narrow the deny rule:
{ "sandbox": { "filesystem": { "denyRead": [] } }}”Sandbox blocked: Write denied”
Section titled “”Sandbox blocked: Write denied””The agent tried to write outside allowWrite. Add the path:
{ "sandbox": { "filesystem": { "allowWrite": [".", "/tmp", "/custom/output"] } }}Network blocked in full mode
Section titled “Network blocked in full mode”Add the domain to allowedDomains:
{ "sandbox": { "mode": "full", "network": { "allowedDomains": ["*.github.com", "api.example.com"] } }}Linux: “bubblewrap not found"
Section titled “Linux: “bubblewrap not found"”sudo apt install bubblewrap socat"Sandbox init failed, continuing unsandboxed”
Section titled “"Sandbox init failed, continuing unsandboxed””The sandbox runtime failed to initialize. The session continues without protection. Check the error message — common causes:
- Linux:
bubblewrapnot installed - macOS: permission issue with
sandbox-exec - Incompatible OS version
Safe Mode & Startup Diagnostics
Section titled “Safe Mode & Startup Diagnostics”PizzaPi connects to external services during startup — MCP servers, the relay server, Claude Code plugins, and hooks. If any of these are slow or unreachable, your CLI session can take a long time to become interactive.
Safe mode lets you skip some or all of these dependencies for an instant startup.
Quick Fix: Safe Mode
Section titled “Quick Fix: Safe Mode”If pizza is hanging or taking too long:
# Skip everything external — instant startuppizza --safe-modeThis disables MCP servers, plugins, hooks, and the relay connection. You get a fully functional coding session without any of the integrations.
Granular Skip Flags
Section titled “Granular Skip Flags”If you only want to skip specific subsystems:
| Flag | What it skips | When to use |
|---|---|---|
--safe-mode | Everything below | Fastest possible startup |
--no-mcp | MCP server connections | MCP server is down or slow |
--no-plugins | Claude Code plugins | Plugin scanning is slow |
--no-hooks | Hook execution | A hook script is hanging |
--no-relay | Relay server connection | Relay is unreachable |
Examples:
# Skip only MCP servers (keep relay, plugins, hooks)pizza --no-mcp
# Skip MCP and plugins but keep relaypizza --no-mcp --no-plugins
# Equivalent to PIZZAPI_RELAY_URL=offpizza --no-relayEnvironment Variables for Runner / Worker Mode
Section titled “Environment Variables for Runner / Worker Mode”When running sessions through the runner daemon, you can pass safe-mode options via environment variables:
| Variable | Effect |
|---|---|
PIZZAPI_NO_MCP=1 | Skip MCP servers |
PIZZAPI_NO_PLUGINS=1 | Skip plugin loading |
PIZZAPI_NO_HOOKS=1 | Skip hook execution |
PIZZAPI_NO_RELAY=1 | Skip relay connection |
What Causes Slow Startup?
Section titled “What Causes Slow Startup?”MCP Servers Most common — Each configured MCP server must spawn, initialize, and respond to tools/list. Common culprits include npx commands that download packages on first run, Docker-based servers that pull images, and unreachable HTTP endpoints. PizzaPi initializes all servers in parallel and applies a per-server timeout (30s default).
// ~/.pizzapi/config.json — increase timeout for slow servers{ "mcpTimeout": 60000}Relay server — If unreachable, the initial handshake can take 10–30 seconds. Fix: --no-relay or "relayUrl": "off".
Plugins — Plugin discovery scans multiple directories. Usually fast but can be slow on network-mounted drives. Fix: --no-plugins.
Hooks — A hanging hook script blocks startup. Fix: --no-hooks or fix the script.
Slow-Startup Warnings
Section titled “Slow-Startup Warnings”When MCP initialization takes longer than 5 seconds, PizzaPi shows a warning with a per-server timing breakdown:
⏱ MCP startup took 12.3sSlow servers: • playwright: 8.2s • tavily: 4.1s (timed out)
Tip: Use --safe-mode or --no-mcp for instant startup.Suppress this warning if you expect slow startup:
{ "slowStartupWarning": false}Startup Diagnostics Configuration
Section titled “Startup Diagnostics Configuration”| Key | Type | Default | Description |
|---|---|---|---|
mcpTimeout | number | 30000 | Per-server timeout (ms) for MCP tools/list calls. 0 to disable. |
slowStartupWarning | boolean | true | Show warnings when startup exceeds 5 seconds. |