sandbox

package
v0.22.168 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

bubblewrap (bwrap) adapter — Linux primary engine.

Wrap rewrites the supplied *exec.Cmd to invoke bwrap with the flags compiled from Profile, then exec the original binary inside the sandbox. We never run unsharing logic ourselves; per ADR-007 bwrap owns the namespace setup, FS bind-mounts, and capability scrubbing. clawtool's polish layer is the Profile→argv translator.

Lifecycle:

  • Wrap mutates cmd.Path + cmd.Args. The original binary path becomes the trailing argument bwrap exec's.
  • cmd.Env is REPLACED with the env-allowlisted subset (bwrap itself --setenv preserves; we also re-build cmd.Env for callers that consult Process.Env directly).
  • sysproc.ApplyGroupWithCtxCancel is the caller's job (supervisor.dispatch). On ctx cancel, the process group SIGKILL reaps bwrap + the agent inside it.

Docker fallback — ADR-020. Available on every OS as long as the daemon is reachable. v0.18.3 lands the actual `docker run` translation (volume mounts for paths, --network none/host for network policy, --memory / --cpus / --pids-limit for limits).

Lives outside any //go:build tag so the adapter is registered on every platform; Available() does the real probe.

install_hints.go — operator-friendly install instructions for the sandbox engines `clawtool sandbox doctor` reports as MISSING. Per ADR-020 §Resolved (2026-05-02): the doctor flow surfaces hints; it never drives an install. Auto-install would require sudo and silently widen the trust surface — operators must run the install command themselves so the credential prompt + package source stays under their control.

Shape: InstallHint(goos, engine) → multi-line string. Empty string means "no hint applies" (engine name unknown for that OS, or the engine is intrinsic and never missing — e.g. noop).

Wired into `clawtool sandbox doctor` (see internal/cli/sandbox.go) so each engine reported Available=false gets the matching hint appended to the human output. JSON output is unchanged — the hint is operator-facing prose, not a wire field.

Package sandbox — runner.go is the one-shot execution helper shared by `clawtool sandbox run` (CLI escape hatch) and the SandboxRun MCP tool (chat-driven callers). Both surfaces want the same primitive: take a parsed Profile + (command, args, stdin), spawn it inside the host-native engine, capture stdout/stderr/exit_code, return after a deadline.

Intentionally tiny — engines already do the heavy lifting via Engine.Wrap; this file only adds:

  • exec.Cmd construction with stdin / pipe wiring
  • timeout + process-group reaping (mirrors the Bash tool's contract: output preserved across SIGKILL)
  • a wire-shape struct callers can serialize directly

Lives next to sandbox.go so it picks up engineRegistry + SelectEngine without importing core / cli (no cycle risk — callers in CLI / MCP tool layers consume RunOneShot).

Package sandbox implements ADR-020. Engine adapters wrap an exec.Cmd with host-native isolation primitives — bwrap on Linux, sandbox-exec on macOS, Docker as a portable fallback, noop where nothing is available.

Per ADR-007 each engine shells out to its primitive's binary; we never re-implement seccomp / AppContainer / namespaces.

v0.18 (this iteration) ships the surface + Engine interface + Profile parser + a working noop engine. Real bwrap / sandbox-exec / docker adapters land in v0.18.1+ — the same incremental pattern v0.16.4 used for `mcp` before v0.17.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InstallHint added in v0.22.135

func InstallHint(goos, engine string) string

InstallHint returns operator-friendly multi-line install instructions for (goos, engine), or an empty string when no hint applies. Caller checks `if hint := InstallHint(...); hint != "" { ... }` and renders the hint verbatim.

Recognised engines: "bwrap", "sandbox-exec", "docker", "noop". Recognised goos: "linux", "darwin", "windows". Anything else returns "".

NOTE: hints describe operator-driven installs. We never run sudo, never invoke a package manager, never download a binary on the operator's behalf. The function returns prose; the human types the command.

Types

type Engine

type Engine interface {
	// Name is the engine's identifier — e.g. "bwrap",
	// "sandbox-exec", "docker", "noop". Surfaced in
	// `clawtool sandbox doctor` output.
	Name() string

	// Available reports whether the engine's underlying primitive
	// is usable on this host (binary on PATH, kernel feature
	// present, etc.).
	Available() bool

	// Wrap mutates cmd so it runs inside the engine's sandbox
	// using the supplied profile. Caller still calls cmd.Start /
	// cmd.Wait — Wrap doesn't run anything itself.
	Wrap(ctx context.Context, cmd *exec.Cmd, profile *Profile) error
}

Engine wraps an exec.Cmd with sandbox constraints.

func SelectEngine

func SelectEngine() Engine

SelectEngine picks the primary engine available on this host, or the noop engine when nothing is. Engines are registered by per-OS init() calls into engineRegistry.

type EngineStatus

type EngineStatus struct {
	Name      string
	Available bool
}

AvailableEngines returns every registered engine's Available status. Used by `clawtool sandbox doctor`.

func AvailableEngines

func AvailableEngines() []EngineStatus

type EnvPolicy

type EnvPolicy struct {
	Allow []string
	Deny  []string
}

EnvPolicy filters host env vars. Both Allow and Deny accept glob patterns matched via filepath.Match. Allow is checked first; Deny then trims matching entries from the result.

type Limits

type Limits struct {
	Timeout      time.Duration // 0 = no per-call timeout
	MemoryBytes  int64         // 0 = unconstrained
	CPUShares    int           // 0 = unconstrained
	ProcessCount int           // 0 = unconstrained (cgroup pids.max)
}

Limits packages the resource caps.

type NetworkPolicy

type NetworkPolicy struct {
	// Mode is one of: "none" | "loopback" | "allowlist" | "open".
	Mode string
	// Allow is honoured only when Mode == "allowlist". Each
	// entry is "host:port" — engines translate to nft rules /
	// pf anchors / docker --add-host depending on the primitive.
	Allow []string
}

NetworkPolicy describes egress restrictions.

type PathMode

type PathMode string

PathMode controls the bind-mount visibility.

const (
	ModeReadOnly  PathMode = "ro"
	ModeReadWrite PathMode = "rw"
	ModeNone      PathMode = "none"
)

type PathRule

type PathRule struct {
	Path string
	Mode PathMode
}

PathRule is one filesystem entry. Path is resolved against the caller's CWD when relative; engines bind it into the sandboxed view at the same logical location.

type Profile

type Profile struct {
	Name        string
	Description string
	Paths       []PathRule
	Network     NetworkPolicy
	Limits      Limits
	Env         EnvPolicy
}

Profile is the typed view of one [sandboxes.<name>] block. Engines convert this into their primitive's flags.

func ParseProfile

func ParseProfile(name string, cfg config.SandboxConfig) (*Profile, error)

ParseProfile turns a config.SandboxConfig into a typed Profile. Returns a clear error per malformed field so the wizard / CLI can surface exactly what the operator typed wrong.

type RunRequest added in v0.22.124

type RunRequest struct {
	Profile *Profile
	Command string
	Args    []string
	Stdin   string
	Timeout time.Duration
}

RunRequest is the typed input for RunOneShot. Profile is the already-parsed sandbox profile (caller did config lookup + ParseProfile); Command + Args are what to actually exec inside the sandbox; Stdin is an optional payload piped to the child.

Timeout = 0 means "use the engine's default" (60s). Negative values are clamped to the default.

type RunResult added in v0.22.124

type RunResult struct {
	Stdout   string `json:"stdout"`
	Stderr   string `json:"stderr"`
	ExitCode int    `json:"exit_code"`
	TimedOut bool   `json:"timed_out"`
	Engine   string `json:"engine"`
	Profile  string `json:"profile"`
}

RunResult is the wire-shape every RunOneShot caller surfaces — CLI's `sandbox run` JSON path and the SandboxRun MCP tool both serialize this directly. Field names mirror the Bash tool's response so chat-driven callers can compose the two without a translation layer.

func RunOneShot added in v0.22.124

func RunOneShot(ctx context.Context, req RunRequest) (RunResult, error)

RunOneShot spawns command+args inside the engine selected for this host, wrapping with profile, with the supplied stdin and timeout. Output is captured even when the deadline kills the child — same contract as core.executeBash, just sandbox-aware.

Returns an error only on setup failure (no command, profile nil, engine wrap rejection). Process exit codes — including non-zero — are returned via RunResult.ExitCode without an error so callers can render a structured failure rather than crashing on a SIGTERM exit-status.

Directories

Path Synopsis
Package egress is the HTTP/HTTPS allowlist proxy that sandbox workers route their network traffic through (ADR-029 phase 4, task #209).
Package egress is the HTTP/HTTPS allowlist proxy that sandbox workers route their network traffic through (ADR-029 phase 4, task #209).
Package worker — daemon-side client for the sandbox worker (ADR-029 phase 1).
Package worker — daemon-side client for the sandbox worker (ADR-029 phase 1).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL