brainwires-sandbox 0.11.0

Container-based sandboxing (Docker/Podman/host) for tool execution in the Brainwires framework
docs.rs failed to build brainwires-sandbox-0.11.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

brainwires-sandbox

Container-based sandboxing for tool execution in the Brainwires Agent Framework. Provides a Sandbox trait with Docker / Podman implementations (via bollard) that isolate tool invocations from the host: resource limits, read-only root filesystems, egress-allowlist networking, and whitelisted bind mounts.

┌──────────────────────┐     spawn()        ┌─────────────────────┐
│  ChatAgent /         │ ──────────────▶    │  SandboxPolicy      │
│  brainwires-tool-*   │                    │  - resource caps    │
└──────────────────────┘                    │  - mount whitelist  │
                                            │  - NetworkPolicy    │
                                            └──────────┬──────────┘
                                                       │
                                                       ▼
                                            ┌─────────────────────┐
                                            │  DockerSandbox      │
                                            │  (bollard client)   │
                                            └──────────┬──────────┘
                                                       │
                                        ┌──────────────┼──────────────┐
                                        ▼              ▼              ▼
                                ┌──────────────┐┌──────────────┐┌──────────────┐
                                │  tool        ││  sidecar     ││  internal    │
                                │  container   ││  proxy       ││  docker net  │
                                │  (read-only) ││  (allowlist) ││  (no egress) │
                                └──────────────┘└──────────────┘└──────────────┘

Features

Flag Default Enables
docker on DockerSandbox (bollard-backed; works with Docker and Podman sockets).
unsafe-host off HostSandbox pass-through. Development only. No isolation.

Quick start

use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Duration;
use brainwires_sandbox::{
    DockerSandbox, ExecSpec, NetworkPolicy, Sandbox, SandboxPolicy,
};

# async fn demo() -> brainwires_sandbox::Result<()> {
let policy = SandboxPolicy {
    image: "ghcr.io/brainwires/brainclaw-sandbox:latest".into(),
    network: NetworkPolicy::Limited(vec!["pypi.org".into()]),
    memory_limit_mb: Some(512),
    cpu_limit: Some(1.0),
    pid_limit: Some(128),
    ..SandboxPolicy::default()
};

let sandbox = DockerSandbox::connect(policy)?;
let spec = ExecSpec {
    cmd: vec!["python".into(), "-c".into(), "print(2 + 2)".into()],
    env: BTreeMap::new(),
    workdir: PathBuf::from("/"),
    stdin: None,
    mounts: vec![],
    timeout: Duration::from_secs(10),
};
let handle = sandbox.spawn(spec).await?;
let out = sandbox.wait(handle).await?;
assert_eq!(out.exit_code, 0);
# Ok(()) }

API

Sandbox trait

#[async_trait]
pub trait Sandbox: Send + Sync {
    async fn spawn(&self, spec: ExecSpec) -> Result<ExecHandle>;
    async fn wait(&self, handle: ExecHandle) -> Result<ExecOutput>;
    async fn shutdown(&self) -> Result<()>;
    fn runtime(&self) -> SandboxRuntime;
}
  • spawn — create the container, attach stdio, validate every mount against the policy (path components are checked for .. traversal and each source is canonicalized to close the symlink-race window), start the workload. Returns an opaque ExecHandle.
  • wait — stream stdout/stderr, await the container exit. Enforces ExecSpec.timeout via tokio::time::timeout; on elapse, kill_container is called explicitly and SandboxError::Timeout is returned with tracing::warn carrying container_id, elapsed_ms, and timeout_ms.
  • shutdown — force-remove every container still tracked by this sandbox instance; tears down per-spawn networks and proxy sidecars.

SandboxPolicy

Field Purpose
runtime Docker, Podman, or Host (unsafe-host feature only).
image Container image for workloads.
network None, Limited(Vec<String>), or Full.
cpu_limit Cores (e.g. 2.0). None disables.
memory_limit_mb Cap in MiB. None disables.
pid_limit Max processes inside the sandbox.
read_only_rootfs Default true.
workspace_mount Implicitly allowed bind source.
allowed_mount_sources Explicit host-path whitelist for bind mounts.
proxy_image Image for the Limited sidecar proxy.
proxy_listen_port TCP port the proxy listens on inside its container.
proxy_container_name Reuse a named long-lived proxy; default spawns an ephemeral one.

NetworkPolicy

  • None--network=none. No egress, no DNS, no loopback to host.
  • Full — default bridge. No egress controls. Trusted images only.
  • Limited(hosts) — sandbox lives on an internal: true docker network with no default route. A brainwires-sandbox-proxy sidecar is attached to both that network and the bridge; the sandbox receives HTTP_PROXY / HTTPS_PROXY env vars. Only hosts in hosts are forwarded; raw (non-HTTP) TCP is blocked at the network level by design.

Errors

pub enum SandboxError {
    Io(std::io::Error),
    Docker(String),          // feature = "docker"
    Timeout,                  // ExecSpec.timeout exceeded
    PolicyViolation(String),  // whitelist reject, symlink mismatch, etc.
    ExitFailure { code: i32, stderr: String },
    NotAvailable(String),
}

Usage notes

Mount validation is two-stage: SandboxPolicy::validate_mount rejects paths that aren't lexically inside the whitelist or contain .. components; DockerSandbox::spawn then canonicalizes each source and re-validates the resolved path. A symlink swapped between those steps is rejected with SandboxError::PolicyViolation.

Podman is selected by setting policy.runtime = SandboxRuntime::Podman. The socket path is taken from PODMAN_SOCKET (default unix:///run/podman/podman.sock).

HostSandbox (feature unsafe-host) runs the requested command directly on the host with no isolation. It exists for local development only and must never be enabled in production — DockerSandbox::connect refuses SandboxRuntime::Host explicitly.

Consumed by

brainwires-tool-builtins uses the Sandbox trait to execute bash / python tool calls under isolation; brainwires-agent composes per-agent sandbox policies.