close
Spacebot

Sandbox

OS-level filesystem containment and environment sanitization for worker subprocesses.

Sandbox

OS-level containment for builtin worker subprocesses (shell and exec). Prevents workers from modifying the host filesystem, reading inherited environment secrets, and accessing the agent's internal data directory.

How It Works

When a worker runs a shell or exec command, the sandbox wraps the subprocess in an OS-level containment layer before execution. The worker's command runs normally -- it can only read a minimal runtime allowlist plus workspace paths, and can only write to explicitly allowed paths.

Worker calls shell("npm test")
  → Sandbox.wrap() builds a contained command
  → Subprocess runs with:
      - Read access to a minimal system allowlist + workspace
      - Writable access only to the workspace + configured writable paths + /tmp
      - Clean environment (no inherited secrets)
      - HOME set to workspace in sandbox mode, parent HOME in passthrough mode
      - TMPDIR set to /tmp
      - tools/bin prepended to PATH
  → stdout/stderr captured and returned to worker

Two things happen regardless of whether the sandbox is enabled or disabled:

  1. Environment sanitization -- worker subprocesses never inherit the parent's environment variables. Secrets like ANTHROPIC_API_KEY are never visible to workers.
  2. PATH injection -- the persistent tools/bin directory is prepended to PATH so durably installed binaries are always available.

Backends

The sandbox auto-detects the best available backend at startup:

PlatformBackendMechanism
LinuxbubblewrapMount namespaces, PID namespaces, environment isolation
macOSsandbox-execSBPL profile with deny-default policy
Other / not availablePassthroughNo filesystem containment (env sanitization still applies)

If the sandbox is enabled but no backend is available, processes run unsandboxed with a warning at startup. Environment sanitization still applies in all cases.

Linux (bubblewrap)

The default on all hosted instances and most self-hosted Linux deployments. Bubblewrap creates a mount namespace where:

  • A minimal host runtime allowlist is mounted read-only (/bin, /sbin, /usr, /lib, /lib64, /etc, /opt, /run, /nix when present)
  • The persistent tools directory is mounted read-only (if present)
  • The workspace directory is mounted read-write
  • writable_paths entries are mounted read-write
  • /tmp is a private tmpfs per invocation
  • /dev has standard device nodes
  • /proc is a fresh procfs (when supported by the environment)
  • The agent's data directory is masked with an empty tmpfs (no reads/writes)
  • PID namespace isolation prevents the subprocess from seeing other processes
  • --die-with-parent ensures the subprocess is killed if the parent exits

Nested containers (Docker-in-Docker, Fly Machines) may not support --proc /proc. The sandbox probes for this at startup and falls back gracefully -- proc_supported: false in the startup log means /proc inside the sandbox shows the host's process list rather than an isolated view.

macOS (sandbox-exec)

Uses Apple's sandbox-exec with a generated SBPL (Sandbox Profile Language) profile. The profile starts with (deny default) and explicitly allows:

  • Process execution and forking
  • Reading only a backend allowlist (system runtime roots + workspace + configured writable paths + tools/bin)
  • Writing only to the workspace, configured writable paths, and /tmp
  • Network access (unrestricted)
  • Standard device and IPC operations

The agent's data directory is denied for both reads and writes even if it falls under the workspace subtree.

Note: sandbox-exec is deprecated by Apple but remains functional. It's the only user-space sandbox option on macOS without requiring a full VM.

Filesystem Boundaries

When the sandbox is enabled, the subprocess sees:

PathAccessNotes
System runtime allowlistRead-onlyBackend-specific system roots required to execute common tools
Agent workspaceRead-writeWhere the worker does its job
writable_paths entriesRead-writeUser-configured additional paths
{instance_dir}/tools/binRead-onlyPersistent binaries on PATH
/tmpRead-writePrivate per invocation (bubblewrap)
/devRead-writeStandard device nodes
Agent data directoryNo accessMasked/denied to protect databases and config

The data directory protection is important: even if the data directory overlaps with workspace-related paths, it's explicitly blocked. Workers can't read or modify databases or config files at the kernel level. Identity files (SOUL.md, IDENTITY.md, ROLE.md) live in the agent root directory, outside the workspace entirely, so they are naturally inaccessible to workers without needing kernel-level protection.

Environment Sanitization

Worker subprocesses start with a clean environment. The parent process's environment variables are never inherited. This applies in all sandbox modes -- even when the sandbox is disabled, env_clear() strips the environment.

A worker running printenv sees only:

VariableSourceValue
PATHAlways{instance_dir}/tools/bin:{system_path}
HOMEMode-dependentWorkspace path (sandbox enabled), parent HOME (sandbox disabled)
TMPDIRAlways/tmp
USERAlwaysFrom parent (if set)
LANGAlwaysFrom parent (if set)
TERMAlwaysFrom parent (if set)
passthrough_env entriesConfigUser-configured forwarding

Workers never see ANTHROPIC_API_KEY, DISCORD_BOT_TOKEN, SPACEBOT_* internal vars, or any other environment variables from the parent process.

passthrough_env

Self-hosted users who set credentials as environment variables in Docker Compose or systemd can forward specific variables to worker subprocesses:

[agents.sandbox]
passthrough_env = ["GH_TOKEN", "GITHUB_TOKEN", "NPM_TOKEN"]

Each listed variable is read from the parent process environment at subprocess spawn time and injected into the worker's environment. Variables not in the list are stripped.

When the secret store is available, passthrough_env is redundant -- credentials should be stored in the secret store, which injects tool secrets automatically. The field is additive and continues to work alongside the store.

Durable Binaries

On hosted instances, the root filesystem is ephemeral -- machine image rollouts replace it. Binaries installed via apt-get install or similar disappear on the next deploy.

The {instance_dir}/tools/bin directory is on the persistent volume and is prepended to PATH for all worker subprocesses. Binaries placed here survive restarts and rollouts.

Workers are instructed about this in their system prompt:

Persistent binary directory: /data/tools/bin (on PATH, survives restarts and rollouts)
Binaries installed via package managers (apt, brew, etc.) land on the root filesystem
which is ephemeral on hosted instances -- they disappear on rollouts. To install a tool
durably, download or copy the binary into /data/tools/bin.

The GET /agents/tools API endpoint lists installed binaries for dashboard observability:

{
  "tools_bin": "/data/tools/bin",
  "binaries": [
    { "name": "gh", "size": 1234567, "modified": "2026-02-20T14:15:00Z" },
    { "name": "ripgrep", "size": 3456789, "modified": "2026-02-15T10:30:00Z" }
  ]
}

Leak Detection

Secret-pattern detection is enforced at channel egress (outbound user-visible text), not as a hard-stop for worker tool execution.

Channel output is blocked when patterns are detected in:

  • reply tool content
  • Plaintext fallback sends when a model returns raw text instead of calling reply

Worker and branch tool outputs no longer terminate the process on pattern hits. This keeps long-running jobs from failing when secrets are handled internally.

Detected patterns include:

  • OpenAI keys (sk-...)
  • Anthropic keys (sk-ant-...)
  • GitHub tokens (ghp_...)
  • Google API keys (AIza...)
  • Discord bot tokens
  • Slack tokens (xoxb-..., xapp-...)
  • Telegram bot tokens
  • PEM private keys
  • Base64-encoded, URL-encoded, and hex-encoded variants of the above

Detection also covers encoded forms -- secrets wrapped in base64, URL encoding, or hex are decoded and checked against the same patterns.

If a channel egress leak is detected, outbound text is blocked. The raw leaked value is never logged -- only the detection event and a truncated non-reversible fingerprint are recorded for debugging.

OpenCode Workers

OpenCode workers (external coding agent processes) apply the same output scrubbing, and also scan SSE text/tool output for secret patterns for observability:

  1. Output scrubbing (exact-match redaction of known secret values) -- runs first
  2. Leak-pattern scan (regex pattern matching for unknown secrets) -- runs second

The ordering ensures that stored tool secrets are redacted before pattern scanning runs, so expected secret values in worker output don't trigger false positives.

Dynamic Mode Switching

Sandbox mode can be changed at runtime via the API or dashboard without restarting the agent. The Sandbox struct reads the current mode from a shared ArcSwap<SandboxConfig> on every wrap() call.

PUT /agents/config
{
  "sandbox": { "mode": "disabled" }
}

Backend detection runs at startup regardless of the initial mode. If the sandbox starts disabled and is later enabled via the API, bubblewrap/sandbox-exec is already detected and ready to use.

Configuration

[agents.sandbox]
mode = "enabled"                              # "enabled" | "disabled"
writable_paths = ["/home/user/shared-data"]   # additional writable directories
passthrough_env = ["GH_TOKEN"]                # env vars to forward to workers
KeyTypeDefaultDescription
modestring"enabled""enabled" for OS-level containment, "disabled" for passthrough
writable_pathsstring[][]Additional directories workers can write to beyond the workspace
passthrough_envstring[][]Environment variable names to forward from the parent process

See Configuration for the full config reference.

Protection Layers

The sandbox is one layer in a defense-in-depth model:

LayerWhat It DoesScope
Sandbox (filesystem)Read allowlist + writable workspace/writable_paths/tmp; blocks agent data dirShell, exec subprocesses
Env sanitizationClean environment, no inherited secretsAll subprocesses (including passthrough mode)
File tool workspace guardPath validation against workspace boundaryFile tool only (in-process)
Exec env var blocklistBlocks LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc.Exec tool
Leak detectionRegex scan of outbound channel text for secret patternsChannel egress (reply + plaintext fallback)
Output scrubbingExact-match redaction of known secret valuesWorker output, status updates, OpenCode events
Secret storeCategorized credential storage, config resolution, tool secret injectionAll agents
Permissions systemApplication-level tool access controlAll tools

The sandbox and permissions system are complementary. The permissions system controls which tools an agent can use and what paths the LLM is allowed to access at the application level. The sandbox enforces filesystem boundaries at the kernel level for subprocesses that are allowed to run.

On this page