sandbox

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 27 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.

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 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 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) 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) 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) 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 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 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.

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 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