sandbox

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package sandbox is the agent's execution boundary: the port every run's work - shell commands and file operations today, computer-use and plugin code later - goes through rather than touching the host directly. It is the execution analog of state.Provider. Swapping the implementation changes where and under what confinement work runs without changing the tools that sit above it: an in-process directory jail (Local), a hardened container, a microVM, or a remote cloud sandbox are all the same port.

Isolation is a boundary the run model goes through from the first execution path, not bolted on later. The dispatch waist's Admitter decides whether an action may run (the capability chokepoint); the Sandbox is where that grant becomes real confinement. The two compose: governance above, isolation below.

Index

Constants

This section is empty.

Variables

View Source
var ErrDenied = fault.New(fault.Terminal, "sandbox_denied", "operation denied by the sandbox boundary")

ErrDenied is returned when an operation falls outside the sandbox's grant: a path that escapes the root, or (in stronger tiers) a denied syscall or egress. The boundary is default-deny.

View Source
var ErrInsufficientContainment = fault.New(fault.Forbidden, "containment_insufficient",
	"sandbox: the available isolation cannot contain this work; refusing")

ErrInsufficientContainment is returned when a tier is too weak for the work's trust level. It is Forbidden (a policy refusal), not transient: the answer is a stronger tier, not a retry.

View Source
var ErrNoContainerRuntime = fault.New(fault.Forbidden, "container_unavailable",
	"container: no OCI runtime is available on this host")

ErrNoContainerRuntime is returned when no registered OCI engine can run a container on the host. It is Forbidden (a policy refusal), not transient: the fix is to install a container runtime, not to retry.

View Source
var ErrNoMicroVM = fault.New(fault.Forbidden, "microvm_unavailable",
	"microvm: no hardware-virtualization backend is available on this host")

ErrNoMicroVM is returned when no registered backend can provide a hardware boundary on this host. It is Forbidden (a policy refusal), not transient: the answer is to install or enable virtualization, not to retry. Untrusted work is refused, never downgraded.

View Source
var ErrReadGrant = errors.New("sandbox: cannot grant read access to a configured directory")

ErrReadGrant reports that a directory named by WithReadableDir could not be granted to the confined child. It fails the launch and is never absorbed by the best-effort fallback: that fallback exists for a host which cannot establish confinement at all, and letting it swallow this error would turn a failure to apply an explicitly requested access grant into a silent, unconfined run of the very command the grant was for. A caller that sees it must treat the sandbox as unusable rather than retry without it.

View Source
var ErrWriteGrant = errors.New("sandbox: cannot grant write access to a configured directory")

ErrWriteGrant reports that a directory named by WithWritableDir could not be granted to the confined child. Like ErrReadGrant it fails the launch and is never absorbed by the best-effort fallback: a write grant that silently did not apply would leave the child unable to save state it is expected to save, and a fallback that dropped confinement instead would hand it the whole host to write to.

Functions

func Admit

func Admit(sb Sandbox, t Trust) error

Admit reports whether sb may run work of the given trust level, the gate that keeps untrusted code out of a tier that cannot contain it. It returns nil when the sandbox's containment meets or exceeds the requirement, and a Forbidden error naming the gap otherwise.

func CleanStaleProfiles added in v0.1.3

func CleanStaleProfiles(time.Time) (int, error)

CleanStaleProfiles removes nothing off Windows and reports so. Confinement on the other platforms registers no persistent operating-system object: a Linux sandbox lives in a mount and network namespace that dies with its process, and a macOS one is a profile passed to the launcher for the life of the command. Only the Windows AppContainer tier registers a profile that can outlive the sandbox that made it, so only Windows needs a collector. It stays defined on every platform so a caller can sweep on startup without writing a build tag.

func EnsureContainerNetwork

func EnsureContainerNetwork(ctx context.Context, engine OCIEngine, name string) error

EnsureContainerNetwork makes sure a named network exists for a published serving container to join, so the container has a stable named network rather than the engine's shared default bridge. It is idempotent: a network that already exists is reused, not recreated.

This names the network the published container attaches to; it does not by itself block the container's outbound traffic. An engine-internal network would deny egress but also disables the port forwarding a served model needs, so egress is instead kept closed by the runtime's own configuration (it is run offline) and, where required, a governed loopback proxy on this network, not by isolating the network from the host.

func ForwardBridge added in v0.1.3

func ForwardBridge(hostURL string) (childURL, forwardTo string)

ForwardBridge reports how a confined child reaches a bridge listening on the host loopback at hostURL: the URL to hand the child, and the host address the sandbox must forward it to. On Linux the child runs in its own network namespace and cannot reach the host loopback at all, so it is given a fixed in-namespace address and the sandbox forwards that to the host one. A hostURL that does not parse is returned unchanged with no forward, so a caller that passed something unexpected fails loudly downstream rather than silently rewriting it.

func GovernedEgressAvailable added in v0.1.3

func GovernedEgressAvailable() bool

GovernedEgressAvailable reports whether this platform can enforce governed child egress (deny the child's direct egress and force it through the policy proxy). It is true on the platforms with an enforcement leg (Linux, macOS, and Linux under WSL2) and false where one is not present, so a caller can refuse a launch that needs governed egress with an actionable message before it reaches the fail-closed refusal inside a launch.

func LiveProfileCount added in v0.1.3

func LiveProfileCount() int

LiveProfileCount is always zero off Windows, where no profile is ever registered.

func LookPath added in v0.1.3

func LookPath(name string) (string, error)

LookPath resolves a program name against the host's PATH and returns its absolute path. It is the sanctioned resolver for callers outside this package, which may not import os/exec: spawning is confined to the sandbox boundary, and resolving a name is the step before spawning. Resolving does not run anything.

A confined child does not resolve names for itself. The AppContainer it runs in cannot read most of the host, so a bare program name it inherits on PATH does not resolve to anything it may execute, and the launch fails with an error about the name rather than the access. Callers pass the resolved absolute path into a launch and grant the child read access to the directory holding it (see WithReadableDir), so the failure modes are "not installed" and "not reachable from inside the sandbox", which are different problems with different fixes.

func PullImage

func PullImage(ctx context.Context, engine OCIEngine, ref, digest string) error

PullImage ensures a digest-pinned image is present, pulling it by its ref@digest (or the bare digest when no ref is given). The engine resolves the reference to the pinned digest and verifies the bytes against it, so the pull is tamper-evident: a moved tag cannot substitute different content. It is the production ImagePuller the container provisioner drives.

func RegisterContainerDriver

func RegisterContainerDriver(d ContainerDriver)

RegisterContainerDriver adds an OCI engine to the registry in preference order. A nil driver is ignored. It is not safe to call concurrently with selection; drivers register at startup, as the microVM drivers do.

func RegisterDriver

func RegisterDriver(d Driver)

RegisterDriver adds a microVM backend to the registry. Platform adapters call it from init under their build tag; a test calls it to install a fake. A nil driver is ignored.

func RunChildLaunchIfRequested

func RunChildLaunchIfRequested()

RunChildLaunchIfRequested is the other half of filesystem confinement. When this binary is re-executed as a confinement launcher (see reexecConfined), this runs the mount setup and then replaces the process with the real command, so it never returns in that case. In the normal case (not a launcher) it returns immediately and the program continues. The program's entry point must call it before doing any other work, since a launcher process must not run the normal program at all.

func SelectDriver

func SelectDriver() (Driver, Availability, error)

SelectDriver returns the first registered backend that reports itself available, plus its availability, or ErrNoMicroVM with a combined diagnosis when none can. Picking an available backend and refusing when there is none is the no-silent-downgrade rule for the untrusted tier: a guest boots only where the host can genuinely contain it.

func StopContainer

func StopContainer(ctx context.Context, engine OCIEngine, id string) error

StopContainer stops a container by id on the given engine, the cross-invocation teardown for a container a record names but no Serving handle is held for (a later `models stop`). It drives the same trusted engine CLI; the container is the boundary, not this call.

Types

type Availability

type Availability struct {
	// OK is true when the driver can boot a guest with a real hardware boundary on this
	// host right now.
	OK bool
	// Detail explains the verdict: what was found, or what is missing (no KVM device,
	// no hypervisor support, no runtime configured), so an operator knows what to install.
	Detail string
}

Availability reports whether a driver can run a microVM on this host, and why or why not in plain language for an operator. Detection never silently downgrades: a driver that cannot guarantee the hardware boundary reports OK=false with a reason, and the gate refuses the untrusted work rather than running it somewhere weaker.

type CaptureSpec

type CaptureSpec struct {
	// Argv is the program and its arguments, executed directly without a shell. Argv[0]
	// is the binary to run; the rest are passed verbatim.
	Argv []string
	// Stdin, when non-empty, is written to the process's standard input and the stream is
	// then closed, the same off-the-command-line path Command.Stdin uses for a secret.
	Stdin []byte
	// Env grants additional KEY=VALUE variables on top of the sandbox's deny-by-default
	// baseline, the same brokered path WithEnv and StreamSpec.Env feed. A granted value
	// overrides the baseline value for the same key.
	Env []string
}

CaptureSpec is a program to run to completion under the sandbox with its combined standard output and standard error collected and returned. It is the argv counterpart of Command: Command runs a shell line (sh -c / cmd /c), while Capture runs the program directly from its Argv, never through a shell, so a path or flag cannot be reinterpreted as shell syntax. It is for a caller that must run a program directly and read all of its output, the external-agent detection probe being the first: it runs a CLI's --version/--help/status and matches on the text, some of which a CLI writes to stderr.

The deny-by-default environment applies exactly as it does to Exec, Serve, and Stream: the host environment is never inherited, so no secret the agent holds reaches the process unless Env grants it by name.

type Command

type Command struct {
	// Line is the command line, interpreted by the sandbox's shell.
	Line string
	// Stdin, when non-empty, is written to the command's standard input and the stream
	// is then closed. It is the safe way to pass a secret to a command that reads one on
	// stdin (a credential import, a passphrase), because the value travels on a pipe: it
	// is never placed on the command line (argv, visible to other processes through the
	// process table) and never appears in the command's rendered text or any log. A
	// caller handling a credential builds it here rather than interpolating it into Line.
	Stdin []byte
}

Command is a shell command to run inside a sandbox.

type Contained

type Contained interface {
	Containment() Containment
}

Contained is the optional capability a sandbox implements to report how strongly it isolates work. A sandbox that does not implement it is treated as the weakest level, so an unknown tier is never assumed to contain more than it proves.

type ContainerDriver

type ContainerDriver interface {
	// Name identifies the engine for logs, errors, and the spine ("docker", "podman").
	Name() string
	// Detect reports whether this engine can run a container on the host right now,
	// without running one, never silently downgrading.
	Detect() Availability
	// Run starts the container for spec and returns a handle to its loopback endpoint. An
	// error means no container was started.
	Run(ctx context.Context, spec ContainerSpec) (Serving, error)
}

ContainerDriver runs a container for a spec on a specific OCI engine. A driver only executes the composed command and adopts the running container as a Serving handle; the security posture is already fixed by buildContainerArgv and the spec validation, so a driver cannot weaken it. Implementations must be safe for concurrent use.

func ContainerDrivers

func ContainerDrivers() []ContainerDriver

ContainerDrivers returns the registered OCI engines, in registration order.

func NewContainerDriver

func NewContainerDriver(engine OCIEngine, runner Runner) ContainerDriver

NewContainerDriver builds a driver for engine. A nil runner uses the default CLI exec runner; a test supplies a scripted one.

func SelectContainerDriver

func SelectContainerDriver() (ContainerDriver, error)

SelectContainerDriver returns the first registered engine that reports itself available, or ErrNoContainerRuntime with a combined diagnosis when none can. Picking an available engine and refusing when there is none is the no-silent-downgrade rule applied to the container tier.

type ContainerImage

type ContainerImage struct {
	// Ref is the image reference without the digest, e.g. "vllm/vllm-openai:v0.6.0". It is
	// recorded for diagnostics; the digest is what the run actually pins to.
	Ref string
	// Digest is the content digest the image is pinned to, "sha256:" followed by 64 hex
	// characters. The run uses ref@digest so a moved tag cannot substitute a different image.
	Digest string
}

ContainerImage names the image to run and the content digest it must be pinned to. A tag alone is mutable (a registry can move it), so the tier refuses an unpinned image: the digest is the trust anchor, the same discipline the binary-runtime releases use.

type ContainerSpec

type ContainerSpec struct {
	// Image is the digest-pinned image to run.
	Image ContainerImage
	// Guarantees is the security composition (egress, caps, mounts, env) reused from the
	// microVM tier; it is validated the same way, so an untrusted container cannot run
	// with the network open, no memory cap, or a writable host mount.
	Guarantees Guarantees
	// GPU optionally grants the host accelerator.
	GPU GPURequest
	// Network is the name of the host-prepared, egress-controlled network a serving
	// container joins, so its only outbound path is the governed one. It is required when
	// a port is published (a published container cannot run network-isolated) and must be
	// empty otherwise, in which case the container runs with no network at all.
	Network string
	// HostPort and ContainerPort publish the container's server on a host loopback port.
	// ContainerPort 0 means the container publishes nothing and runs network-isolated.
	HostPort      int
	ContainerPort int
	// Command, when set, is the argv run inside the container, appended after the image so
	// it overrides the image's default command (the engine runs `run <opts> <image>
	// <command...>`). For a serving runtime it is the server invocation, for example the
	// vLLM serve arguments. It is argv, never a shell string, so nothing in it is
	// interpreted by a shell. Empty runs the image's own entrypoint and command.
	Command []string
	// ExecScratch makes the in-memory /tmp scratch executable. It is off by default so the
	// container's only writable area cannot also run code, the hardened posture. A runtime
	// that compiles and runs code at startup needs it on: a GPU inference server JIT-compiles
	// its kernels into the scratch directory and must execute them from there, so a noexec
	// scratch makes it fail to start. The scratch is still in-memory and per-container, and
	// the root filesystem stays read-only either way.
	ExecScratch bool
}

ContainerSpec is a request to run a container under the tier's guarantees: which image, the shared security composition, an optional GPU grant, the network it joins, and the loopback port its server is published on.

type Containment

type Containment int

Containment is how strongly a sandbox tier isolates the work inside it, on an ordered scale: a higher level contains strictly more. The critical axis is the last one a level adds, the kernel/hardware exploit boundary, because the worst case for untrusted code is arbitrary execution that escapes a shared kernel.

The level is what lets the run gate decide, before anything executes, whether a tier is strong enough for the work's trust level, and refuse rather than run untrusted code somewhere it could break out.

const (
	// ContainmentNone is a process jail: the work is confined to a directory and runs
	// without the agent's environment, but shares the host kernel, network, and
	// syscalls. Suitable only for trusted code (the agent's own tools).
	ContainmentNone Containment = iota
	// ContainmentKernel adds kernel-enforced confinement of the filesystem, syscalls,
	// and namespaces (Landlock/seccomp, Seatbelt, AppContainer). Suitable for
	// semi-trusted, model-authored code; still a shared kernel.
	ContainmentKernel
	// ContainmentContainer adds a container's namespacing and resource control, but
	// still over a shared host kernel, so it is not a boundary for untrusted code.
	ContainmentContainer
	// ContainmentUserKernel re-implements syscalls in user space off the host kernel
	// (a user-space-kernel sandbox), a strong boundary with some compatibility cost.
	ContainmentUserKernel
	// ContainmentMicroVM gives the work its own kernel on hardware virtualization, so
	// a kernel exploit inside cannot reach the host. The boundary for untrusted code.
	ContainmentMicroVM
	// ContainmentRemote runs the work off this host entirely, the strongest
	// blast-radius separation.
	ContainmentRemote
)

func ContainmentOf

func ContainmentOf(sb Sandbox) Containment

ContainmentOf reports a sandbox's containment level, defaulting to the weakest when the sandbox does not declare one. Defaulting down is the safe direction: an undeclared tier can never satisfy a requirement it has not earned.

func Required

func Required(t Trust) Containment

Required is the minimum containment a trust level may run under. Untrusted code requires a hardware boundary by default, the strict posture: a kernel exploit in, say, a model parser must not be able to reach the host. A policy may later relax untrusted to a user-space-kernel tier explicitly, but the safe default does not.

func (Containment) String

func (c Containment) String() string

String names the level for logs and errors.

type Driver

type Driver interface {
	// Name identifies the backend for logs, errors, and the spine (for example "kvm",
	// "hvf", "hyperv").
	Name() string
	// Detect reports whether this backend can boot a guest with a real hardware boundary
	// on this host right now, without booting one.
	Detect() Availability
	// Boot launches a guest confined to spec.Guarantees and returns it. An error means no
	// guest was created.
	Boot(ctx context.Context, spec Spec) (Machine, error)
}

Driver is a platform's hardware-virtualization backend: it detects whether the host can run a microVM and boots a confined guest. Each platform registers its driver in init under its build tag, so the core binary carries only the detection surface and a platform pulls in only its own VMM primitives.

func Drivers

func Drivers() []Driver

Drivers returns the registered microVM backends, in registration order.

type ExecResult

type ExecResult struct {
	Output   string // combined stdout and stderr
	ExitCode int
}

ExecResult is the outcome of a Command. A non-zero ExitCode is a normal result, not an error: the command ran and the caller reads its output and code. An error from Exec means the command could not be run at all.

type GPURequest

type GPURequest struct {
	// Enabled exposes the GPU to the container.
	Enabled bool
	// Device, when set, scopes access to specific device ids (e.g. "0" or "0,1"). Empty
	// with Enabled exposes all GPUs.
	Device string
}

GPURequest grants a container access to the host GPU. It is off by default: a container sees no accelerator unless one is explicitly requested, and the host must have the passthrough tooling (checked by the caller against the hardware capability probe).

type Guarantees

type Guarantees struct {
	// EgressDenied withholds all outbound network from the guest, so an inference
	// process cannot exfiltrate data or reach a command-and-control host. It must be
	// true for the untrusted posture; the network is denied at the virtualization
	// boundary, not merely inside the guest.
	EgressDenied bool
	// Limits caps the guest's CPU, memory, processes, and lifetime.
	Limits Limits
	// Mounts are the host paths exposed to the guest; for an untrusted guest each one
	// must be read-only.
	Mounts []Mount
	// Env is the explicit, secret-free set of variables the guest sees. The host
	// environment is never inherited, so a secret the agent holds cannot leak into the
	// guest; only what is named here is passed.
	Env map[string]string
}

Guarantees is the non-negotiable security composition every microVM workload runs under: the egress posture, the resource caps, the secret-free environment, and the read-only host mounts. The tier validates them before a guest boots and refuses a spec that would weaken the untrusted-code posture, so a caller cannot accidentally launch a guest with the network open, unbounded memory, or a writable host mount. This is the boundary's aggregation of every defense-in-depth layer in one place.

func Untrusted

func Untrusted(lim Limits, mounts ...Mount) Guarantees

Untrusted builds the Guarantees for running untrusted code (a downloaded model's runtime, an unsigned plugin): egress denied, the given resource caps, and every host mount forced read-only. It is the safe constructor, so a caller does not assemble the untrusted posture by hand and risk leaving a field open.

type Image

type Image struct {
	// Kernel is the absolute host path to the guest kernel to boot.
	Kernel string
	// RootFS is the absolute host path to the guest root filesystem image.
	RootFS string
}

Image names the guest's kernel and root filesystem, the bytes the VMM boots. Flynn does not ship a hypervisor or a guest image; a host provisions them and names them here, so the tier stays a thin, auditable boundary over whatever virtualization the host already trusts.

type Limits

type Limits struct {
	// VCPUs is the number of virtual CPUs the guest may use; a value <= 0 means a
	// single vCPU, the safe floor.
	VCPUs int
	// MemMiB is the guest's RAM ceiling in MiB. It must be > 0: a microVM with no
	// memory cap is refused.
	MemMiB int
	// PIDs caps the number of processes in the guest, bounding a fork bomb; a value
	// <= 0 applies the driver's default cap rather than leaving it unbounded.
	PIDs int
	// Wall is a hard wall-clock lifetime for the guest, after which it is torn down
	// regardless of progress; 0 leaves the lifetime to the caller's context.
	Wall time.Duration
}

Limits caps a guest's resource use, the runaway and cost breaker enforced at the virtualization boundary rather than left to the guest. A guest with no memory bound is refused: an unbounded VM is a denial-of-service surface, not a contained one.

type Local

type Local struct {
	// contains filtered or unexported fields
}

Local is the in-process sandbox tier: the floor that runs everywhere with no host support required. It confines all file operations to a working-directory root (a path that escapes via "..", an absolute path, or a symlink pointing outside is denied) and runs commands in that directory. It is not strong isolation on its own - a command it runs otherwise has the host user's privileges outside the working tree. WithNetworkDenied, WithReadOnlyFS, and WithSeccomp add kernel-enforced network, filesystem, and syscall isolation where the platform supports it; the container, microVM, and remote tiers build on the same Sandbox port. Local is always present underneath them as the default-deny FS floor.

func NewLocal

func NewLocal(dir string, opts ...LocalOption) (*Local, error)

NewLocal builds a Local sandbox rooted at dir. The root is resolved to an absolute, symlink-free path once, so confinement checks compare against a stable base.

func (*Local) Capture

func (l *Local) Capture(ctx context.Context, spec CaptureSpec) (ExecResult, error)

Capture runs spec.Argv to completion as a confined subprocess and returns its combined output and exit code. It is the run-to-completion argv launch path, alongside Exec (a shell line), Serve (backgrounded), and Stream (streaming). A non-zero exit is a normal result carried in ExecResult.ExitCode, not an error; an error means the command could not be run at all.

Confinement follows the same rules as Exec: whatever isolation the Local was configured for is applied (a governed-egress Local always takes the confined path so its OS-level egress denial holds), and, unless this Local is the always-on best-effort baseline, a confinement that cannot be established fails the launch rather than dropping to an unconfined run. The best-effort fallback, when it applies, never weakens a governed-egress or network-denied launch.

func (*Local) Close

func (l *Local) Close() error

Close releases resources. The in-process tier holds none itself, but a platform's confinement may leave persistent state (a registered container profile on Windows), and a governed-egress sandbox holds a running proxy; both are released here. closePlatform is a no-op where there is nothing to release.

func (*Local) ConfinementTier added in v0.1.3

func (l *Local) ConfinementTier() string

ConfinementTier names the mechanism that will confine a command this sandbox runs, for the record a governed run keeps. It reports the tier the configuration selects, not a weaker fallback a host might drop to, and it is a name rather than a level: two tiers can bound a code-execution exploit equally (both report ContainmentKernel) while confining reads differently, and a record that stored only the level could not tell them apart.

func (*Local) Containment

func (l *Local) Containment() Containment

Containment reports how strongly this Local confines the commands it runs. The ordered level measures the critical axis: the kernel-enforced filesystem and syscall confinement that bounds a code-execution exploit, which is what separates a bare process jail from a kernel-confined tier. By default it is a process jail (ContainmentNone): it confines paths and scrubs the environment but shares the host kernel and syscalls, so it is for trusted code only. When the read-only host and the syscall filter are both enabled and the platform enforces them, it rises to ContainmentKernel: the command cannot change the host filesystem or make a dangerous syscall, the boundary for semi-trusted, model-authored code over a shared kernel.

Network egress is deliberately not part of the level. It is a separate axis governed by the egress policy (a child network namespace, or the outbound gate the agent's own requests pass through), so a kernel-confined tier that legitimately allows a command to use the network still reports kernel. Folding network into the level would mean the secure default, which leaves egress to the per-run policy rather than denying it wholesale, could never report the tier it actually provides. The level never claims ContainmentKernel where the platform cannot enforce the confinement, so it never outruns what actually holds.

func (*Local) Exec

func (l *Local) Exec(ctx context.Context, cmd Command) (ExecResult, error)

Exec runs a command through a per-OS shell in the working directory. A non-zero exit is returned as a result, not an error; only a failure to start (or a cancelled context) is an error.

When confinement was requested as the secure-by-default baseline (see WithDefaultConfinement) and the kernel refuses to set it up on this host, for example where unprivileged user namespaces are restricted, the command is run again at the process-jail floor rather than failing. The default baseline is always-on, so it degrades to the floor it can always provide; an explicitly requested confinement still fails loudly, since the caller asked for it by name.

func (*Local) Glob

func (l *Local) Glob(_ context.Context, pattern string) ([]string, error)

Glob lists confined paths matching a shell pattern, relative to the root.

func (*Local) ReadFile

func (l *Local) ReadFile(_ context.Context, path string) ([]byte, error)

ReadFile reads a confined file. The open is symlink-safe (see confinedOpen): a path component swapped to an escaping symlink after the resolve check cannot redirect the read outside the root.

func (*Local) Root

func (l *Local) Root() string

Root returns the absolute working directory the sandbox is confined to.

func (*Local) Serve

func (l *Local) Serve(_ context.Context, spec ServeSpec) (*Process, error)

Serve starts spec.Argv as a background process in the sandbox working directory with the deny-by-default environment (the host's environment is never inherited, exactly as for Exec), and returns a handle once the process has started. A failure to start is an error; a process that starts and later exits is reported through the handle, not here. When spec.Confine is set, the process is launched under whatever kernel confinement this Local was configured for, so a runtime parsing untrusted weights runs read-only to the host and behind the syscall filter where the platform enforces them.

Confinement here is expressed on the child process the standard library starts, the path Linux and macOS use for a backgrounded server as well as a one-shot command. Where a platform cannot carry its confinement onto a backgroundable handle (Windows, whose AppContainer is applied through a blocking launch), an explicitly confined Serve is refused rather than started at the directory-jail floor under a tier the trust gate relied on; only the always-on best-effort baseline is allowed to drop to the floor, matching the fallback rule Exec uses.

func (*Local) Session added in v0.1.3

func (l *Local) Session(ctx context.Context, spec SessionSpec) (*Session, error)

Session launches spec.Argv as a confined duplex subprocess and returns a handle to its stdin and stdout pipes, its stderr tail, and its lifecycle. Confinement follows the same rules as Exec, Stream, and Serve: a governed-egress Local always takes the confined path so its OS-level egress denial holds; an explicit Confine request is honoured where the platform can enforce it and, unless this Local is the always-on best-effort baseline, a confinement that cannot be established fails the launch rather than dropping to an unconfined run (refuse-rather-than-downgrade). Where a platform cannot carry its confinement onto a still-writable background process, a confined Session is refused rather than run unconfined, exactly as Serve refuses a confined background server.

func (*Local) Stream

func (l *Local) Stream(ctx context.Context, spec StreamSpec) (*StreamProcess, error)

Stream launches spec.Argv as a confined subprocess and returns a handle to its live stdout, bound to ctx: cancelling ctx kills the process. It is the streaming launch path, alongside Exec (one-shot) and Serve (backgrounded). Confinement follows the same rules as Exec: a governed-egress Local always takes the confined path so its OS-level egress denial holds; an explicit Confine request is honored where the platform can enforce it and, unless this Local is the always-on best-effort baseline, a confinement that cannot be established fails the launch rather than dropping to an unconfined run (refuse-rather-than-downgrade). The best-effort fallback, when it applies, never weakens a governed-egress or network-denied launch.

func (*Local) Walk

func (l *Local) Walk(_ context.Context, root string) ([]string, error)

Walk returns the regular-file paths under root, relative to the sandbox root.

func (*Local) WritableDirs added in v0.1.3

func (l *Local) WritableDirs() []string

WritableDirs reports the directories outside the workspace that a confined child of this sandbox may write, in the resolved form the grants were made against. It is the audit surface of WithWritableDir: these are the only host locations a confined child could have changed, so a caller can state them next to the confinement tier rather than leaving the widening implicit. The slice is a copy, so a caller cannot widen the grants by writing to it.

func (*Local) WriteFile

func (l *Local) WriteFile(_ context.Context, path string, data []byte) error

WriteFile writes a confined file, creating parent directories. The open is symlink-safe (see confinedOpen): a symlink swapped into the target path after the resolve check cannot redirect the write outside the root, and a terminal symlink is refused rather than written through.

type LocalOption

type LocalOption func(*Local)

LocalOption configures a Local sandbox.

func WithDefaultConfinement

func WithDefaultConfinement() LocalOption

WithDefaultConfinement is the secure-by-default baseline: it enables the kernel confinements the current platform can enforce, and nothing where it cannot, so it is safe to apply unconditionally. A command is confined where the OS supports it and runs at the process-jail floor elsewhere, never refused merely for the default asking. It enables the read-only host and the syscall filter, the two confinements that hold for ordinary commands without breaking them: a command still reads the host and works in its directory, it just cannot change the host or make a dangerous syscall. Network denial is deliberately not part of the default, since a blanket no-network policy would break commands a run is legitimately allowed to make; that is governed per run instead. Explicit options (WithReadOnlyFS, WithSeccomp, WithNetworkDenied) still fail loudly on a platform that cannot honor them; this one does not, because it is the always-on baseline rather than an explicit request.

func WithEgress

func WithEgress(policy netguard.Policy) LocalOption

WithEgress governs the outbound network of every child the sandbox launches through policy: the child is pointed at a loopback proxy that enforces policy, and its direct egress is denied at the OS level so the proxy is the only way out. It is the OS-level reuse of the same netguard policy that guards the agent's own dials. On a platform whose enforcement leg is not present, a launch under this option refuses rather than running with the network silently open (refuse-rather-than-weaken), exactly as WithNetworkDenied does.

func WithEnv

func WithEnv(vars map[string]string) LocalOption

WithEnv grants additional environment variables into commands the sandbox runs. A command's environment is otherwise a minimal, secret-free baseline (see Exec): the host's environment is never inherited, so the agent's own credentials are withheld by construction. This is the only way a variable beyond the baseline reaches a command, the brokered path a capability grant feeds. A granted value overrides the baseline value for the same key. Secrets must not be granted here; they belong in a request the sandbox never sees, not a child's environment.

func WithExecTimeout

func WithExecTimeout(d time.Duration) LocalOption

WithExecTimeout caps how long a single command may run (0, the default, applies no cap beyond the caller's context). It is the integration point the resource-limit and circuit-breaker layer builds on.

func WithHostReadable added in v0.1.3

func WithHostReadable() LocalOption

WithHostReadable selects the confinement tier that confines writes to the workspace but leaves the host filesystem readable to the child, for a program the deny-by-default read posture would break. Writes are still kernel-enforced: outside the workspace the child cannot create, modify, or delete anything.

It is a deliberate, recorded weakening of the read posture, so a caller names it only for a child that needs it. Linux and macOS already read-permit under WithReadOnlyFS, so there the option only makes that explicit and changes no behavior. On Windows it replaces the AppContainer with a write-restricted token, because the container's read posture is not merely strict but incomplete: a contained token cannot query the drive-letter objects that map an NT path back to a DOS path, so canonicalizing any path fails inside it. Programs that canonicalize on startup (every Rust program calling std::fs::canonicalize, external agent CLIs among them) cannot run under the container at all, and no filesystem grant can fix it, because the object denied is not a file.

Callers that gate on containment should record which tier ran (see ConfinementTier): both are kernel-enforced over a shared kernel and report ContainmentKernel, but this one does not confine reads.

func WithKernelConfinement

func WithKernelConfinement() LocalOption

WithKernelConfinement enables the full kernel-confined tier in one call: no network, a read-only host, and the syscall filter together. A Local built with it reports ContainmentKernel where the platform enforces the confinement, the level suitable for semi-trusted, model-authored code. It is the same as passing WithNetworkDenied, WithReadOnlyFS, and WithSeccomp.

func WithLoopbackForward added in v0.1.3

func WithLoopbackForward(hostAddr string) LocalOption

WithLoopbackForward forwards one host-loopback address into the confined child's network namespace, reachable from the child at a fixed in-namespace loopback address. It is the inbound counterpart of WithEgress and, on Linux, depends on it: the forward lives on the same network namespace the egress leg creates. The child reaches exactly the one host address named here and nothing else on the host loopback, so the run's own MCP bridge is reachable while every other local service on the box is not.

On a platform whose child shares the host's network stack (macOS) the child reaches the host loopback directly, so ForwardBridge reports no forward is needed and this option is not configured. It exists on every platform so callers compile uniformly.

func WithNetworkDenied

func WithNetworkDenied() LocalOption

WithNetworkDenied runs commands with no network access, the OS counterpart of the in-process egress policy: a command the sandbox runs cannot reach out (no exfil, no command-and-control), which matters most for code we do not fully trust. It is enforced by the kernel, by running the command in a network namespace with no interfaces, so the command sees only a down loopback and no routes. On a platform or host that cannot provide it, a command run under this option fails rather than running with the network silently still open.

func WithReadOnlyFS

func WithReadOnlyFS() LocalOption

WithReadOnlyFS runs commands against a read-only view of the host: the command can read what the host user can, but the only thing it can write is its own working directory (and a private scratch area). A command we do not fully trust therefore cannot tamper with host files, plant a persistent change outside the working tree, or modify another project. It is enforced by the kernel, by running the command in a mount namespace where every host mount is read-only and only the working tree is remounted writable. On a platform that cannot provide it, a command run under this option fails rather than running with the host filesystem silently still writable.

func WithReadableDir

func WithReadableDir(paths ...string) LocalOption

WithReadableDir grants a confined child read access to a directory outside the workspace. By default confinement is default-deny for reads on the platforms that can enforce it (a Windows AppContainer can read only what it is granted), so a governed external CLI cannot reach its own auth or config home, which lives outside the sandbox root. Naming that directory here grants the child read (and traverse) on it for the life of the sandbox and revokes the grant on Close. The access is read-only: the child still cannot write outside the workspace.

On Linux and macOS a read-only host already permits reads of the whole filesystem, so the child reads the directory in place and this option adds nothing; it takes effect only where the confinement denies reads by default. The path is resolved to an absolute, symlink-free form once, like the root. Passing a path keeps a credential in its home directory rather than copying it into the workspace.

func WithResourceLimits

func WithResourceLimits(r ResourceLimits) LocalOption

WithResourceLimits caps the memory and process count of commands the sandbox runs, on top of the wall-clock cap. The zero value applies no cap, so a caller that does not set it is unaffected. See ResourceLimits for the per-platform enforcement and why the always-on floor does not impose a hard memory cap by default.

func WithSeccomp

func WithSeccomp() LocalOption

WithSeccomp runs commands under a syscall filter that refuses the calls a command working in its own directory has no honest need for and that would let it escalate privilege, escape its confinement, tamper with the kernel, or reach into other processes. Ordinary file, process, memory, and IO calls are left allowed, so normal commands run unaffected; a refused call fails with a permission error rather than killing the command. It is enforced by the kernel and inherited across the exec. On a platform that cannot provide it, a command run under this option fails rather than running with no syscall filter in place.

func WithWritableDir added in v0.1.3

func WithWritableDir(paths ...string) LocalOption

WithWritableDir grants a confined child write access to a directory outside the workspace, in addition to the workspace itself. Every confinement tier denies writes outside the workspace by default, so an external CLI that must persist its own state (a refreshed OAuth token, a session rollout, a log) has nowhere to put it. Naming that directory here grants the child write on it for the life of the sandbox and revokes the grant on Close.

This is the one option that widens what a confined child can change on the host, so it is deliberately not part of any default tier and the caller names an exact directory. Point it at a directory the run owns and can destroy, never at a host location whose contents outlive the sandbox: a confined child is untrusted, and whatever it can write it can also corrupt. The path is resolved to an absolute, symlink-free form once, like the root, so a symlink swapped in later cannot redirect the grant.

type Machine

type Machine interface {
	// Exec runs a command to completion inside the guest and returns its result. A
	// non-zero exit is a result, not an error; an error means the command could not be
	// run at all.
	Exec(ctx context.Context, line string) (ExecResult, error)
	// Serve starts a long-lived process inside the guest and returns a handle. The
	// guest's loopback endpoint is forwarded to the host address the handle reports, so
	// a model server in the guest is reachable without giving the guest host network.
	Serve(ctx context.Context, argv []string) (Serving, error)
	// ReadFile reads a file from the guest working area.
	ReadFile(ctx context.Context, p string) ([]byte, error)
	// WriteFile writes a file into the guest working area, creating parents.
	WriteFile(ctx context.Context, p string, data []byte) error
	// List returns the regular-file paths under dir inside the guest, relative to its
	// root. It is the primitive Glob and Walk build on.
	List(ctx context.Context, dir string) ([]string, error)
	// Close shuts the guest down and releases the VM's resources.
	Close() error
}

Machine is a running microVM a driver provides: the minimal primitives the tier needs to use a guest with its own kernel. It is the boundary a hardware-virtualization backend implements, so a backend writes only this surface and the guest is already booted confined to its Guarantees by the driver that created it. Implementations must be safe for concurrent use.

type MicroVM

type MicroVM struct {
	// contains filtered or unexported fields
}

MicroVM is the hardware-virtualization sandbox tier: it implements the Sandbox port over a Machine the host's VMM provides, so isolation runs in a guest with its own kernel. It reports ContainmentMicroVM, the level the gate requires for untrusted work, and confines every file operation to the guest root as defense-in-depth on top of the guest's own boundary, matching the Local and Remote tiers' default-deny rule.

func BootMicroVM

func BootMicroVM(ctx context.Context, spec Spec) (*MicroVM, error)

BootMicroVM selects an available backend, validates the spec's guarantees, boots a confined guest, and returns the tier wrapping it. It refuses (never downgrades) when no backend can provide a hardware boundary, or when the guarantees are weaker than the untrusted posture, so a guest that boots is always genuinely contained.

func BootMicroVMWith

func BootMicroVMWith(ctx context.Context, d Driver, spec Spec) (*MicroVM, error)

BootMicroVMWith boots a guest on a specific backend, the path the model runner uses once it has selected a driver, and the path a test uses with a fake driver. It applies the same guarantee validation as BootMicroVM, so neither route can launch a weakened guest.

func (*MicroVM) Close

func (v *MicroVM) Close() error

Close tears the guest down and releases the VM's resources.

func (*MicroVM) Containment

func (v *MicroVM) Containment() Containment

Containment reports microVM: a guest with its own kernel on hardware virtualization, a code-execution exploit inside cannot reach the host kernel. This is the only level the gate admits untrusted work at.

func (*MicroVM) Driver

func (v *MicroVM) Driver() string

Driver names the backend this guest booted on, for the spine and diagnostics.

func (*MicroVM) Exec

func (v *MicroVM) Exec(ctx context.Context, cmd Command) (ExecResult, error)

Exec runs a command inside the guest. The guest, not this method, is the boundary, so the command runs under the guest's own kernel, egress posture, and resource caps.

func (*MicroVM) Glob

func (v *MicroVM) Glob(ctx context.Context, pattern string) ([]string, error)

Glob lists guest paths matching a shell pattern, relative to the root, matched with path.Match so behavior does not depend on a guest shell.

func (*MicroVM) ReadFile

func (v *MicroVM) ReadFile(ctx context.Context, p string) ([]byte, error)

ReadFile reads a confined file from the guest working area.

func (*MicroVM) Serve

func (v *MicroVM) Serve(ctx context.Context, argv []string) (Serving, error)

Serve starts a long-lived process inside the guest and returns its handle, the path a model server runs through. The server is reachable on the handle's host loopback address; the guest itself still has no outbound network.

func (*MicroVM) Walk

func (v *MicroVM) Walk(ctx context.Context, root string) ([]string, error)

Walk returns the regular-file paths under a confined root inside the guest.

func (*MicroVM) WriteFile

func (v *MicroVM) WriteFile(ctx context.Context, p string, data []byte) error

WriteFile writes a confined file into the guest working area.

type Mount

type Mount struct {
	// HostPath is the absolute host path to expose. It must be absolute so the mount
	// cannot be reinterpreted relative to the guest's working directory.
	HostPath string
	// GuestPath is the absolute path the mount appears at inside the guest.
	GuestPath string
	// ReadOnly gives the guest a read-only view of HostPath. For an untrusted guest
	// every host mount must be read-only.
	ReadOnly bool
}

Mount is a host path exposed inside the guest. Untrusted weights are mounted read-only so the guest cannot tamper with them or use a writable mount to plant a persistent change on the host. A writable host mount into an untrusted guest is a hole in the boundary, so the tier refuses one (see Guarantees.validate).

type OCIEngine

type OCIEngine string

OCIEngine is a container runtime Flynn drives. Both accept the same run flags this tier composes, so the engine choice does not change the security posture, only the binary.

const (
	// EngineDocker is the Docker CLI.
	EngineDocker OCIEngine = "docker"
	// EnginePodman is the Podman CLI, a drop-in for the run flags used here.
	EnginePodman OCIEngine = "podman"
)

type Process

type Process struct {
	// contains filtered or unexported fields
}

Process is a handle to a background process started by Serve. It is safe for concurrent use: Stop, Wait, Done, and Output may be called from any goroutine. The process is not tied to the context passed to Serve; it runs until it exits on its own or Stop is called, so a server outlives the call that started it.

func (*Process) Done

func (p *Process) Done() <-chan struct{}

Done returns a channel closed when the process exits, so a caller can select on it alongside a health check or a context.

func (*Process) Output

func (p *Process) Output() string

Output returns the retained tail of the process's combined stdout and stderr, for surfacing why a server failed to come up.

func (*Process) PID

func (p *Process) PID() int

PID returns the operating-system process id, for a registry record.

func (*Process) Running

func (p *Process) Running() bool

Running reports whether the process has not yet exited.

func (*Process) Stop

func (p *Process) Stop() error

Stop ends the process and waits for it to exit. It is idempotent: calling it after the process has already exited returns nil. A kill is used rather than a graceful signal because the server holds no state worth flushing and a prompt, certain stop is what a lifecycle command needs.

func (*Process) Wait

func (p *Process) Wait(ctx context.Context) error

Wait blocks until the process exits or ctx is done. It returns the process's exit error (nil on a clean exit), or ctx.Err() if the context fires first. The process is not killed on a context timeout; the caller decides whether to Stop it.

type Remote

type Remote struct {
	// contains filtered or unexported fields
}

Remote is the remote sandbox tier: it implements the Sandbox port by mapping each operation onto a Transport, so isolation runs server-side in a cloud microVM while the host only orchestrates. It is platform-agnostic (the host never executes the work) and the same on Linux, macOS, and Windows. Path confinement is enforced here as defense-in-depth before any call reaches the backend, matching the Local tier's default-deny boundary.

func NewRemote

func NewRemote(t Transport) *Remote

NewRemote builds a Remote sandbox over a backend transport.

func (*Remote) Close

func (r *Remote) Close() error

Close tears down the remote sandbox.

func (*Remote) Exec

func (r *Remote) Exec(ctx context.Context, cmd Command) (ExecResult, error)

Exec runs a command in the remote sandbox.

func (*Remote) Glob

func (r *Remote) Glob(ctx context.Context, pattern string) ([]string, error)

Glob lists remote paths matching a shell pattern, relative to the root. The pattern is matched against each listed path with path.Match (no "**"), so it behaves consistently across backends rather than depending on a remote shell.

func (*Remote) ReadFile

func (r *Remote) ReadFile(ctx context.Context, p string) ([]byte, error)

ReadFile reads a confined file from the remote sandbox.

func (*Remote) Walk

func (r *Remote) Walk(ctx context.Context, root string) ([]string, error)

Walk returns the regular-file paths under a confined root, relative to the sandbox root.

func (*Remote) WriteFile

func (r *Remote) WriteFile(ctx context.Context, p string, data []byte) error

WriteFile writes a confined file in the remote sandbox.

type ResourceLimits

type ResourceLimits struct {
	// MemoryMiB caps the memory the command (and its job, where the platform caps a
	// tree) may commit, in MiB. Zero applies no memory cap.
	MemoryMiB int
	// MaxProcesses caps how many processes the command may have running at once, a
	// fork-bomb backstop. Zero uses the platform's generous default cap.
	MaxProcesses int
}

ResourceLimits caps a sandboxed command's use of host resources beyond the wall-clock cap (WithExecTimeout): the memory it may commit and the number of processes it may spawn. A zero field applies no cap on that axis, so the zero value changes nothing and the always-on floor stays out of a legitimate build's way. It is the always-present tier's coarse backstop against a memory bomb or a fork storm; the container and microVM tiers enforce hard CPU, memory, and PID caps for untrusted work, where the runtime gives every axis a real cgroup.

Enforcement is per platform and best effort at this tier. Windows applies it through the confined command's job object (a real memory and active-process cap). The Linux and macOS floors do not yet impose it (a per-run cgroup needs cgroup-v2 delegation that is not guaranteed for an unprivileged agent); a command that needs a hard cap there runs under the container or microVM tier.

type Runner

type Runner func(ctx context.Context, argv []string) (stdout string, err error)

Runner runs an OCI engine command to completion and returns its stdout. argv[0] is the engine binary, the rest its arguments. It is injected so the driver's logic is testable with a scripted engine; the default runner execs the CLI.

type Sandbox

type Sandbox interface {
	// Exec runs a command inside the sandbox.
	Exec(ctx context.Context, cmd Command) (ExecResult, error)
	// ReadFile reads a file within the sandbox.
	ReadFile(ctx context.Context, path string) ([]byte, error)
	// WriteFile writes a file within the sandbox, creating parent directories.
	WriteFile(ctx context.Context, path string, data []byte) error
	// Glob lists paths matching a shell pattern within the sandbox, relative to the
	// root.
	Glob(ctx context.Context, pattern string) ([]string, error)
	// Walk returns the regular-file paths under root within the sandbox, relative
	// to the sandbox root.
	Walk(ctx context.Context, root string) ([]string, error)
	// Close releases the sandbox's resources.
	Close() error
}

Sandbox is the execution boundary. All paths are relative to the sandbox root, and an implementation must reject any operation that would reach outside its boundary (default-deny). Implementations must be safe for concurrent use.

Environment isolation is part of the contract: an implementation must not pass the agent's process environment through to a command it runs. A command sees only a minimal, credential-free baseline plus variables granted to it explicitly, so a secret the agent holds can never leak into a command's environment by default. This is the execution-boundary half of the project's "secrets are never materialized into the agent's environment" invariant.

func Select

func Select(t Trust, candidates ...Sandbox) (Sandbox, error)

Select returns the strongest candidate sandbox that may run work of the given trust level, or a Forbidden error when none is strong enough. Choosing the strongest available tier, and refusing when there is none, is the no-silent-downgrade rule: the system runs untrusted work only where it is genuinely contained, or not at all.

type ServeSpec

type ServeSpec struct {
	// Argv is the program and its arguments, executed directly without a shell. Argv[0]
	// is the binary to run; the rest are passed verbatim.
	Argv []string
	// Confine requests the platform's kernel confinement (a read-only host and the
	// syscall filter) for the process where the Local was built to provide it. Network
	// is deliberately left reachable so a loopback server the process binds stays
	// reachable from this host; denying outbound egress while keeping loopback is a
	// separate, finer policy layered on top, not part of starting the server.
	Confine bool
}

ServeSpec describes a long-lived process to run in the background inside the sandbox. It is the counterpart of Command for a server that must keep running and be reached over a loopback port, rather than a one-shot command whose output is collected and returned. The program is run directly from its argv, never through a shell, so a model id or weights path in the arguments cannot be reinterpreted as shell syntax.

type Serving

type Serving interface {
	// Addr is the host loopback address (host:port) the guest's server is reachable at.
	Addr() string
	// Running reports whether the server has not yet exited.
	Running() bool
	// Output returns the retained tail of the server's combined output, for diagnostics.
	Output() string
	// Done is closed when the server exits.
	Done() <-chan struct{}
	// Stop ends the server and tears down its guest. It is idempotent.
	Stop() error
}

Serving is a handle to a long-lived server running inside a guest. It mirrors the background-process contract the serve layer consumes, but the address is a host loopback endpoint forwarded into the guest rather than a direct host port.

func RunContainer

func RunContainer(ctx context.Context, spec ContainerSpec) (Serving, error)

RunContainer validates the spec, selects an available OCI engine, and runs the container, the entry point a runtime uses. It refuses (never downgrades) when the spec would weaken the untrusted posture or when no engine is available, so a container that starts is always both contained and running on a real runtime.

func RunContainerWith

func RunContainerWith(ctx context.Context, d ContainerDriver, spec ContainerSpec) (Serving, error)

RunContainerWith runs a container on a specific engine, the path used once an engine is selected and the path a test uses with a fake engine. It applies the same validation as RunContainer, so neither route can start a weakened container.

type Session added in v0.1.3

type Session struct {
	// contains filtered or unexported fields
}

Session is a running confined subprocess whose stdin and stdout are live pipes and whose stderr is retained as a bounded tail for diagnostics. It is safe for concurrent use: Stdin, Stdout, Stderr, Wait, and Stop may be called from any goroutine. The process runs until it exits on its own or Stop is called; Stop kills it and releases its resources, so an extension leaves no orphan when it is disabled or the host shuts down.

func (*Session) Stderr added in v0.1.3

func (s *Session) Stderr() string

Stderr returns the retained tail of the process's standard error, for surfacing why an extension misbehaved or failed to come up. It is bounded, so a chatty process cannot grow it without limit.

func (*Session) Stdin added in v0.1.3

func (s *Session) Stdin() io.WriteCloser

Stdin is the process's standard input, held open for the caller to write requests to. Closing it signals end-of-input to the process; Stop closes it as part of teardown.

func (*Session) Stdout added in v0.1.3

func (s *Session) Stdout() io.Reader

Stdout is the process's live standard output, for the caller to read replies from. It must be drained for the process to make progress.

func (*Session) Stop added in v0.1.3

func (s *Session) Stop() error

Stop ends the process and waits for it to exit. It is idempotent and safe to call more than once. It cancels the launch context (killing the process), closes the stdin pipe, and reaps the process so no descendant is left behind.

func (*Session) Wait added in v0.1.3

func (s *Session) Wait() error

Wait blocks until the process exits and returns its outcome (nil on a clean exit, a non-nil error on a non-zero exit or a Stop-driven kill). It is idempotent: the OS-level wait runs once and its result is memoised, so Wait and Stop may both be called.

type SessionSpec added in v0.1.3

type SessionSpec struct {
	// Argv is the program and its arguments, executed directly without a shell. Argv[0] is
	// the binary to run; the rest are passed verbatim. For an extension it is a fixed,
	// signature-verified path with fixed arguments, never model-influenced.
	Argv []string
	// Env grants additional KEY=VALUE variables on top of the deny-by-default baseline. A
	// granted value overrides the baseline value for the same key. An extension is launched
	// with a minimal env and no secrets; a scoped bridge token, when one is needed, reaches
	// the child here rather than on the command line.
	Env []string
	// Confine requests the platform's kernel confinement (a read-only host and the syscall
	// filter, plus the network isolation the Local was configured for). It composes with a
	// governed-egress Local: when egress is configured the launch is confined regardless of
	// this flag, so the OS-level denial that pins the child to the proxy is always in force.
	Confine bool
}

SessionSpec describes a long-lived process to run inside the sandbox with BOTH its standard input and output held open as live pipes, for a caller that carries on a request/response conversation with the process over its own stdio. It is the duplex counterpart of Stream (stdin written once then closed, stdout streamed) and Serve (backgrounded, reached over a loopback port): the out-of-process extension host speaks MCP JSON-RPC to a subprocess and must both write requests to it and read replies from it for the life of the process.

The channel is deliberately the process's own anonymous stdio pipes and nothing else: a pipe fd is private to this parent and its child, where a loopback port any co-located process could bind or capture is not, so an extension cannot be snooped or injected on its channel. The program is run directly from Argv, never through a shell, so nothing in the arguments can be reinterpreted as shell syntax, and the deny-by-default environment applies exactly as for Exec, Stream, and Serve: the host environment is never inherited, so no secret the agent holds reaches the process unless Env grants it by name.

type Spec

type Spec struct {
	// Root is the host working directory the guest's output and staging area live in.
	// File operations through the Sandbox port are confined to it, the same default-deny
	// boundary the Local and Remote tiers enforce.
	Root string
	// Image is the guest kernel and root filesystem to boot.
	Image Image
	// Guarantees is the security composition the guest must run under.
	Guarantees Guarantees
}

Spec is a request to boot a confined guest: where its working area lives on the host, which image to boot, and the guarantees it runs under.

type StreamProcess

type StreamProcess struct {
	// contains filtered or unexported fields
}

StreamProcess is a running confined subprocess: its stdout as a live stream plus a wait handle. Its lifetime is bound to the context passed to Stream, so cancelling that context (a halt or a shutdown) kills the process and Wait then returns. Stdout must be drained for the process to make progress; Wait blocks until the process ends and returns its outcome (nil on a clean exit, a non-nil error on a non-zero exit or a context-driven kill). A caller that has finished reading before the process exits calls Wait to release its resources.

func (*StreamProcess) Stdout

func (p *StreamProcess) Stdout() io.Reader

Stdout is the process's live standard output. Standard error is not part of the stream (an agent CLI reports its events on stdout); it is discarded.

func (*StreamProcess) Wait

func (p *StreamProcess) Wait() error

Wait blocks until the process exits and returns its outcome. It also releases the process's resources, so it must be called once the caller is done with the process, including after a context-driven kill.

type StreamSpec

type StreamSpec struct {
	// Argv is the program and its arguments, executed directly without a shell. Argv[0]
	// is the binary to run; the rest are passed verbatim.
	Argv []string
	// Stdin, when non-empty, is written to the process's standard input and the stream is
	// then closed. Like Command.Stdin it keeps a secret off the command line: the value
	// travels only on the pipe, never in argv or a log.
	Stdin []byte
	// Env grants additional KEY=VALUE variables on top of the sandbox's deny-by-default
	// baseline, the same brokered path WithEnv feeds a one-shot command. A granted value
	// overrides the baseline value for the same key. It is how a scoped, single-episode
	// bridge token reaches the child without ever touching the command line.
	Env []string
	// Confine requests the platform's kernel confinement (a read-only host and the syscall
	// filter, plus the network isolation the Local was configured for). It composes with a
	// governed-egress Local: when egress is configured the launch is confined regardless of
	// this flag, so the OS-level denial that pins the child to the proxy is always in force.
	Confine bool
}

StreamSpec is a program to launch under the sandbox with its stdout delivered as a live stream and its lifetime bound to a context, for a caller that reads the process's output as it is produced and stops it on a halt. It is the streaming counterpart of Command (one-shot, combined output collected and returned) and ServeSpec (backgrounded, only a buffered tail kept): the external-agent episode host reads an agent CLI's newline-delimited event stream and kills the CLI when the run halts.

The program is run directly from its Argv, never through a shell, so a model id, a path, or any other value in the arguments cannot be reinterpreted as shell syntax. The deny-by-default environment applies exactly as it does to Exec and Serve: the host environment is never inherited, so no secret the agent holds reaches the process unless Env grants it by name.

type Transport

type Transport interface {
	// Exec runs a shell command in the remote working directory. A non-zero exit is
	// a result (in ExecResult.ExitCode), not an error; an error means the command
	// could not be run.
	Exec(ctx context.Context, line string) (ExecResult, error)
	// ReadFile reads a file from the remote working directory.
	ReadFile(ctx context.Context, p string) ([]byte, error)
	// WriteFile writes a file in the remote working directory, creating parents.
	WriteFile(ctx context.Context, p string, data []byte) error
	// List returns the regular-file paths under dir (recursive), relative to the
	// remote root. It is the primitive Glob and Walk build on, so a backend exposes
	// one listing call rather than two.
	List(ctx context.Context, dir string) ([]string, error)
	// Close tears down the remote sandbox and releases its resources.
	Close(ctx context.Context) error
}

Transport is the minimal set of primitives a remote sandbox backend provides: run a command, move a file in or out, list files, and tear the sandbox down. It is the Flynn-owned port each cloud backend (E2B, Daytona, Modal, ...) implements over its own API or CLI, so a backend writes only this surface and inherits the full Sandbox port through Remote. Paths are relative to the remote sandbox root; the backend confines them server-side, and Remote adds a default-deny check on top so confinement never depends on the backend alone.

type Trust

type Trust int

Trust is how far the code about to run is trusted, which sets the containment it requires.

const (
	// TrustTrusted is the agent's own built-in tools: code we ship and vet.
	TrustTrusted Trust = iota
	// TrustSemi is code the model authored this run (a shell command, a script):
	// not hostile by construction, but not vetted either.
	TrustSemi
	// TrustUntrusted is code from outside that we cannot vouch for: a downloaded
	// model's weights parsed by a runtime with a history of remote-code-execution
	// flaws, or an unsigned plugin. The worst case is host compromise.
	TrustUntrusted
)

func (Trust) String

func (t Trust) String() string

String names the trust level for logs, errors, and provenance.

Jump to

Keyboard shortcuts

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