Sandbox a run

Every agent (swarm) stage must declare how it’s contained — pond fails the stage closed if it doesn’t. This guide is the practical “which knobs do I set”; specs/sandbox-isolation has the internals.

The sandbox lives on the stage’s executor:

"sandbox": {
  "profile": "untrusted-code-write",
  "tier": "trusted",
  "backend": "firecracker",
  "allow_hosts": ["git.acme.com"],
  "overrides": { "image": "your/agent:latest", "memory": "4g", "cpus": "2" }
}

1. Pick a profile

The profile fixes the isolation posture — you can’t loosen it with overrides.

ProfileFilesystemNetworkUse when
nonehost, openopenlocal dev only — never for untrusted code
untrusted-code-readread-only checkoutmodel API only (broker)the agent only needs to read + call the model
untrusted-code-writethrowaway writable copydefault-deny allowlistthe agent must edit, build, test, or install deps

untrusted-code-write is the full coding harness: edits land in a disposable copy (discarded after), and the agent can pip/npm/cargo install and git because the allowlist opens those — while everything else is blocked.

none is an explicit opt-out, not a default. It runs the agent unconfined (host network + filesystem + the run’s credential in env) — fine only for code you trust. A none stage always raises an unconfined_sandbox preflight warning, and a deployment that runs untrusted code should set POND_REQUIRE_SANDBOX=true, which refuses none outright (preflight 422 + dispatch fail-closed) so nothing reaches a worker unconfined by accident.

2. Let installs through (allowlist egress)

On untrusted-code-write, egress is default-deny. Safe defaults already allow the common registries (pypi, npm, crates) + GitHub. Add anything else — e.g. the target’s own git remote or a private registry — with allow_hosts:

"allow_hosts": ["git.acme.com", "artifactory.acme.com"]

The model API is always reachable (via the broker). Cloud metadata and private IP ranges are always blocked, even if a host resolves to them.

3. Choose an isolation backend

backend selects how strong the boundary is. Omit it to route to any sandbox-capable worker (sandbox.*); set it to require a specific one:

backendBoundaryNotes
dockershared-kernel containerfine for trusted hosts
gvisoruserspace kernel (runsc)stronger, runs on most hosts
firecrackermicroVM (Kata + Firecracker)strongest; for untrusted multi-tenant

A worker that can’t actually enforce the requested backend won’t be offered the job — it’s fail-closed, never a silent downgrade.

Stand up a gVisor worker in one commanddeploy/gvisor-worker.sh installs + registers runsc and runs a worker advertising sandbox.gvisor (with a --check preflight). See Deploying pond Isolation. Run it on a spare Linux box, not your control plane: untrusted code executes in the runsc-isolated sibling, off your trusted network.

4. Cap resources

overrides tunes resource/image knobs (only — it can never widen the posture):

  • image — the OCI image the agent runs in (bake your toolchain here).
  • memory, cpus, pids_limit, tmpfs_size — per-run limits.

Run-level budgets (wall-clock, cost ceiling) are operator settings, not stage config — see Configuration (POND_RUN_*); they auto-cancel a runaway run.

5. Pick a trust tier

tier decides which pool may run the stage. Untrusted-code profiles default to trusted. Set byo to run on a bring-your-own host. See Untrusted & BYO workers.

6. Require a containment floor for the whole run

The sandbox block above is per stage — it’s where that stage chooses its posture. When a run handles genuinely hostile input (e.g. malware analysis), you want a stronger guarantee: a run-level minimum no stage can fall below, and proof it was honoured. That’s the containment floorrequiredPosture on the run (not the stage):

"requiredPosture": { "min_backend": "firecracker", "require_confined": true }
  • min_backend — a minimum on the isolation order docker < gvisor < firecracker.
  • require_confined — the enforced profile must be confined (not none).

Both keys are optional; an absent floor means “no constraint” (the default). From the CLI:

pond run submit --project $P --source <git-url> --harness codex --model-ref orx \
  --profile untrusted-code-write --min-backend firecracker --require-confined

The floor is enforced fail-closed at two points:

  1. At dispatch. The stage’s posture is pinned up to the floor (max(stage, floor)) and the job will only route to a worker advertising at least sandbox.<min_backend>. If no attached worker can meet it, the run fails fast with a clear reason (pond run logs) rather than landing somewhere weaker.
  2. At the result. The worker signs the posture it actually enforced into its result attestation (under the key pinned at enrollment). Pond verifies it and fails the run (posture_violation) if the proven posture is missing, unsigned, or below the floor — so a worker that misreports its containment can’t have its result accepted.

This is software-rooted: it defends an untrusted host/network, misconfig, misrouting, and a downgraded config, but not a maliciously patched worker binary (that’s the future hardware-attestation tier). See run-trust containment floor for the threat model.

Minimal vs hardened

// dev: run an agent locally, unconfined
"sandbox": { "profile": "none" }

// production: untrusted code, microVM, installs allowed, capped
"sandbox": { "profile": "untrusted-code-write", "tier": "trusted",
             "backend": "firecracker",
             "overrides": { "image": "your/agent:latest", "memory": "4g" } }

See also: Worker pool · specs/sandbox-isolation.