runner

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package runner defines the target-agnostic sandbox lifecycle contract. The control plane ONLY talks to this interface — it must contain zero Docker- or Kubernetes-specific code. Drivers live in subpackages (currently runner/docker) and are conformance-tested identically.

Index

Constants

View Source
const (
	DefaultCPUMillis int64 = 2000 // 2 vCPU
	DefaultMemoryMiB int64 = 4096 // 4 GiB hard memory cap
	DefaultPidsLimit int64 = 512  // max processes/threads (fork-bomb guard)
)

Conservative platform resource defaults, applied when a Resources field is zero. They exist so EVERY agent sandbox is capped even when policy sets nothing: without them one runaway or prompt-injected agent can OOM-kill the host, fork-bomb the host PID space, or fill host storage and take sibling runs down with it (the basic multi-tenant safety controls — see types.ResourceLimits). A policy value always overrides.

View Source
const AgentIdleScript = `` /* 978-byte string literal not displayed */

AgentIdleScript is the agent sandbox's main (idle) process for NON- interactive runs: it installs the per-run TLS-MITM CA (when delivered) and then idles while the task Exec does the real work. (Interactive runs use `agent-run --idle` as their main process instead — each substrate's CreateSandbox wires this — which performs this same CA install plus workspace prep; the human then drives claude in the attach shell.) The CA install is REQUIRED either way: without it, NODE_EXTRA_CA_CERTS points at a CA file that was never written, so claude cannot trust the proxy's TLS termination of api.anthropic.com (breaking subscription proxy-side injection). It writes the EXACT paths internal/api pins (/tmp/wardyn — any- uid-writable, so it works regardless of the image's USER/HOME; this Cmd may run as root while agent-run later re-runs as the image user, hence the sticky-bit dir and the ||true rewrites: within one run the content is identical, so a failed rewrite over a correct file is harmless). It also assembles the COMBINED bundle (system roots + per-run CA) that SSL_CERT_FILE/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE point at — those vars REPLACE the client trust store, so the bare CA there would break non-MITM'd CONNECT-tunneled hosts. Keep in lockstep with install_mitm_ca in deploy/images/common/agent-run-lib.sh. No-op when the run did not opt into TLS-MITM (WARDYN_MITM_CA_PEM unset).

View Source
const ProxyListenPort = 3128

ProxyListenPort is the port wardyn-proxy listens on inside the per-run internal network/namespace. The agent's ONLY reachable address.

Variables

View Source
var ErrExecStreamUnsupported = errors.New("runner: ExecStream not supported")

ErrExecStreamUnsupported is the sentinel a Runner/Substrate returns from ExecStream when it has no implementation for the primitive (a stub, a fake, or a driver that has not wired it up yet). Callers — notably the conformance suite's testExecStream — use errors.Is against this exact sentinel to skip cleanly on "not implemented" while still FAILING on any other error, so an implemented-but-broken ExecStream cannot skip green by returning some other error.

Functions

func BuildProxyConfig added in v0.5.0

func BuildProxyConfig(runID uuid.UUID, pc ProxyConfig, port int) ([]byte, error)

BuildProxyConfig marshals a run's ProxyConfig (egress policy, MITM CA, injection rules, run token, ...) into the JSON payload every substrate delivers to its wardyn-proxy sidecar as WARDYN_PROXY_CONFIG_JSON — the proxy fails closed without it (no policy => no working egress). Pure data transform (field mapping + json.Marshal); hoisted out of the docker driver so a k8s substrate builds byte-identical sidecar config without duplicating the mapping. port is the sidecar's listen port (ProxyListenPort in production; parameterized for tests).

func IsKnownNonVaultRuntime added in v0.5.0

func IsKnownNonVaultRuntime(name string) bool

IsKnownNonVaultRuntime reports whether name is a runtime positively known to deliver less than a VM boundary (the CC3/Vault floor guard). Prefix match so "runc"/"crun-foo"/"sysbox-runc"/"runsc-*" are all caught. Note "krun" is NOT matched by the "crun" family (different leading byte), so libkrun (crun + libkrun, a real KVM microVM) stays eligible for CC3.

func RecorderArgv added in v0.5.0

func RecorderArgv(castDir, outDir, uploadURL string, runID uuid.UUID, agentArgv []string) []string

RecorderArgv builds the argv an agent sandbox runs when session recording is enabled. It delegates to wardyn-rec (a thin binary inside the agent image) so the GPL recorder (asciinema) is exec'd as a subprocess, never linked into Wardyn (license hygiene).

Layout:

wardyn-rec -cast-dir <dir> [-out-dir <mount>] [-upload-url <proxy route>] -run <id> -- <agent argv...>

wardyn-rec decides at runtime whether asciinema is present; the caller does not need to know. uploadURL is the DEFAULT delivery path: the proxy's brokered recording route (PUT /wardyn/v1/recordings/{run}), which injects the run token and lets the control plane MASK secrets before persisting the cast (secret masking lives control-plane-side; the registry of secret values is never in the sandbox). outDir is the optional shared-mount fallback and carries TWO documented limitations: (1) every agent sandbox sharing the mount runs under the same identity, so it has NO cross-run isolation; and (2) it bypasses the control plane, so the cast it writes is UNMASKED — secret masking is structurally impossible here (wardyn-rec holds no secret values, by design). Use the brokered upload path where recordings are viewer-exposed.

HIGH-finding hardening: the masked upload path and the unmasked shared-mount -out-dir are MUTUALLY EXCLUSIVE. When an uploadURL is configured we drop the shared-mount -out-dir entirely so wardyn-rec can NEVER also drop an UNMASKED <runID>.cast into the API-served replay store (cross-run-writable, viewer- exposed). The shared mount is only ever used as the reduced-isolation FALLBACK when no control-plane upload path exists.

Callers should only invoke this when recording is enabled — there is no passthrough case; an unwrapped argv is simply argv itself.

func ValidateMount

func ValidateMount(m Mount) error

ValidateMount enforces the host bind-mount deny-list. It returns nil for an allowed mount and a descriptive error (suitable for an HTTP 400 or a fail-closed CreateSandbox error) for any denied one. It is the single source of truth shared by the policy validator and the docker driver.

Rules (all fail closed):

  1. Source must be a non-empty, absolute, already-cleaned path (path.Clean is idempotent on it) — no "..", no relative path.
  2. Source must not be "/" and must not equal or be nested under any deniedSourcePrefixes entry.
  3. Source must not reference a Docker socket (any path whose base is docker.sock), wherever it lives.
  4. Target must be a non-empty, absolute, cleaned path under one of allowedTargetPrefixes.

func ValidateMountSource added in v0.4.4

func ValidateMountSource(src string) error

ValidateMountSource enforces rules 1-3 above — the SOURCE half of ValidateMount, extracted so a surface that only vets a reusable host path (workspace onboarding, the driver's recording-mount probe) can run the same deny-list without inventing a placeholder target. Source half ONLY: any surface that actually BINDS the path must call ValidateMount.

func ValidateTarget

func ValidateTarget(tgt string) error

ValidateTarget enforces the in-container mount/clone target shape: a non-empty, absolute, cleaned path under one of allowedTargetPrefixes. It is the target half of ValidateMount, extracted so another authoring surface that places something at an in-container path WITHOUT a host bind-mount source (e.g. a git-cloned WorkspaceRepo target) can enforce the same invariant without duplicating the prefix list.

Types

type AttachOptions

type AttachOptions struct {
	Cols uint16
	Rows uint16
}

AttachOptions configures an interactive attach. Cols/Rows are the initial PTY window size; zero values let the driver pick a sane default (e.g. 80x24).

type Capabilities

type Capabilities struct {
	Driver string `json:"driver"` // e.g. "docker"
	// ConfinementClasses available on this host/cluster, strongest last.
	ConfinementClasses []types.ConfinementClass `json:"confinement_classes"`
	// Resolved maps each available ConfinementClass to the concrete substrate
	// label that enforces it (e.g. "oci/runc", "oci/runsc", "oci/kata-qemu"), so
	// /healthz can advertise WHICH runtime backs each class — the seam that makes
	// CC3 substrate-pluggability visible to operators. Nil when a driver does not
	// report substrate detail.
	Resolved map[types.ConfinementClass]string `json:"confinement_substrates,omitempty"`
	// StructuralEgress reports L0 support: sandbox has no default route and
	// its only egress path is the wardyn-proxy sidecar.
	StructuralEgress bool `json:"structural_egress"`
	// NetworkPolicy reports L1 support (nftables / NetworkPolicy default-deny).
	NetworkPolicy bool `json:"network_policy"`
	// SessionRecording reports wardyn-rec sidecar support.
	SessionRecording bool `json:"session_recording"`
}

Capabilities declares what a driver (on this host/cluster) can actually enforce. The control plane uses this to honor Confinement Class policy: it must refuse to schedule a run whose policy demands more than the driver declares. Never claim a control that is not structurally enforced.

type ExecSession added in v0.5.0

type ExecSession struct {
	// Stdin writes to the exec's standard input. Close HALF-closes the write
	// side only (the exec observes EOF on stdin) — implementations MUST NOT
	// tear down Stdout/Stderr when Stdin.Close is called; those streams may
	// still be flowing.
	Stdin io.WriteCloser
	// Stdout carries standard output. With ExecSpec.TTY=true it ALSO carries
	// stderr (PTY semantics merge the two onto one stream); with TTY=false it
	// carries ONLY stdout — see Stderr.
	Stdout io.Reader
	// Stderr carries standard error as a SEPARATE stream when ExecSpec.TTY is
	// false. When TTY is true there is no separate stderr channel to read
	// (PTY semantics already merged it onto Stdout), so Stderr reads io.EOF
	// immediately.
	//
	// STREAMING CONTRACT (TTY=false): Stdout and Stderr are UNBUFFERED
	// io.Pipes fed by a single background demux goroutine off ONE underlying
	// connection: a single undrained stderr byte blocks the demux goroutine,
	// Stdout, AND Wait (which observes the same exec). Callers MUST start
	// draining Stderr BEFORE (or concurrently with) the first Stdout read.
	// This mirrors how every demultiplexed docker/moby attach stream must be
	// consumed; it is not implementation-specific.
	Stderr io.Reader
	// Resize changes the PTY window size. A no-op returning nil when the exec
	// has no TTY.
	Resize func(cols, rows uint16) error
	// Wait blocks until the exec exits and returns its exit code.
	Wait func() (int, error)
	// Close tears down ONLY this exec stream — never the sandbox, the agent
	// process, or any sidecar.
	Close func() error
}

ExecSession is a live, bidirectional stream into ONE exec started by Runner.ExecStream — the streaming-primitive analogue of Session (which is shaped for an interactive attach shell). It is a PLAIN STRUCT of streams/closures, deliberately NEVER an interface: ExecStream has exactly one production implementation per substrate plus test fakes, and a struct lets a fake construct a partial ExecSession (a canned Wait, a strings.Reader for Stdout, a nil Stdin) by filling in fields directly — there is no method set to satisfy, so no fake can ever "implement this wrong."

Resize, Wait, and Close take NO context parameter: an implementation binds them to whatever context it created the exec with. Callers that need a fresh deadline per operation should scope the ctx passed to ExecStream itself accordingly.

type ExecSpec added in v0.5.0

type ExecSpec struct {
	Argv []string
	// Env is additional exec-scoped environment (on top of the sandbox's own).
	Env []string
	// TTY requests a pseudo-terminal. PTY semantics MERGE stdout and stderr
	// onto ExecSession.Stdout (ExecSession.Stderr then reads io.EOF
	// immediately — there is no separate channel to read). Without a TTY,
	// stdout and stderr are delivered as SEPARATE streams: a binary protocol
	// riding stdout (SFTP, socat) would be corrupted by interleaved stderr
	// bytes, so a merged stream is only acceptable under PTY semantics.
	TTY bool
	// Cols, Rows seed the initial PTY window size (TTY only); zero lets the
	// implementation pick a default.
	Cols, Rows uint16
}

ExecSpec describes one exec launched via Runner.ExecStream: the argv to run, exec-scoped environment, and whether it runs under a PTY.

type ImageChecker added in v0.5.0

type ImageChecker interface {
	// ImagePresent reports whether ref is present in the substrate's local
	// image store right now.
	ImagePresent(ctx context.Context, ref string) (bool, error)
}

ImageChecker is an OPTIONAL Runner capability (W20-W20-record-image-5): a substrate whose local image cache can go stale out from under a workspace's cached image_ref (the docker driver — a pruned/removed local image; the daemon that built it is gone) implements this so a stale cache can be detected and fallen through to a rebuild, instead of the cached ref being trusted forever and every launch failing at "no such image" until an operator finds and clears the row by hand. A substrate that pulls fresh per launch (k8s: the kubelet pulls, there is no local cache to go stale) has nothing to report and simply doesn't implement this — callers type-assert and treat "doesn't implement" the same as a check error: unknown, so trust the cache (fail-open, the same posture resolveWorkspaceImage already takes everywhere else).

type ImageRemover added in v0.5.0

type ImageRemover interface {
	// ImageRemove deletes ref from the substrate's local image store. A ref
	// already absent (raced by a manual prune, a prior partial cleanup) is
	// NOT an error — same idempotent-teardown contract as StopSandbox.
	ImageRemove(ctx context.Context, ref string) error
}

ImageRemover is an OPTIONAL Runner capability (bug-workspace-1): a substrate with a local image store (the docker driver) implements this so a superseded workspace-built image (a rescan, an image-choice edit, or a workspace delete) can be reclaimed instead of leaking a full docker image forever — every resolveWorkspaceImage build lane mints a fresh, uniquely- named local tag on every cache miss and nothing removed the tag it replaced. Best-effort by design (callers log-and-continue on error, the same posture as ImageChecker): a substrate that pulls fresh per launch (k8s) has nothing local to reclaim and simply doesn't implement this.

type InjectionGrant

type InjectionGrant struct {
	GrantID uuid.UUID            `json:"grant_id"`
	Rule    egress.InjectionRule `json:"rule"`
}

InjectionGrant pairs an api_key credential grant with its proxy-side injection rule (host/header/format/secret name — never the secret value).

type Mount

type Mount struct {
	Source   string `json:"source"`
	Target   string `json:"target"`
	ReadOnly bool   `json:"read_only"`
}

Mount is one operator/policy-controlled host bind mount into the sandbox. Source is a host path; Target is the in-container path (drivers restrict it to an allowed prefix, e.g. under /home/agent or /work). ReadOnly defaults to true (RW only when the policy explicitly opts in). See SandboxSpec.Mounts for the security model: mounts are operator/policy-controlled, never request-set.

type ProxyConfig

type ProxyConfig struct {
	// RunToken authenticates the sidecars to the control plane (identity
	// provider verifies; it is NOT a secret usable outside the platform).
	RunToken string
	// ControlPlaneURL is where sidecars stream decisions/recordings.
	ControlPlaneURL string
	// Policy is the run's egress policy, handed verbatim to the wardyn-proxy
	// sidecar (default-deny domain allowlist, method rules, first-use flag).
	// Drivers MUST deliver it to the sidecar at launch: a proxy without a
	// policy fails closed and the sandbox has no working egress at all.
	Policy types.RunPolicySpec
	// Injection lists the run's auto-mintable api_key grants the proxy
	// resolves at startup (secret values live only in proxy memory — never in
	// the sandbox). Approval-gated api_key grants are NOT included: they would
	// block proxy startup, which fails closed if any injection mint fails.
	Injection []InjectionGrant
	// MITMCACertPEM / MITMCAKeyPEM are the OPTIONAL per-run TLS-MITM CA (PEM)
	// delivered to the proxy sidecar when the policy opts into intercept_tls. The
	// CA private key reaches ONLY the proxy (never the sandbox); the sandbox
	// trusts the public cert (delivered separately via the agent env). Empty =>
	// opaque CONNECT passthrough (no MITM).
	MITMCACertPEM string
	MITMCAKeyPEM  string
	// MITMHosts are OPERATOR-CONFIGURED corp artifact hosts the proxy may TLS-MITM
	// in addition to the built-in LLM hosts, so a corporate registry token can be
	// injected on the wire (the sandbox never holds it). A tight per-host operator
	// allowlist sourced from site-config's artifact overrides — NEVER a blanket and
	// NEVER attacker-controlled (dispatch populates it, the sandbox cannot).
	MITMHosts []string
	// MITMLLM reports whether TLS-MITM of the built-in LLM hosts (Anthropic/OpenAI)
	// is intended for this run (subscription injection or intercept_tls) — as opposed
	// to a CA minted only for artifact-token injection. See proxy.Config.MITMLLM.
	MITMLLM bool
	// GitGrants is the git-broker per-repo allowlist ("<org>/<repo>" -> github_token
	// grant id) delivered to the proxy sidecar's /wardyn/gh/ route so the sandbox
	// reaches only its granted repos (never all of github.com) and the token stays
	// proxy-side. Populated at dispatch from the run's github grants; empty => no
	// repo brokered. See proxy.Config.GitGrants.
	GitGrants map[string]uuid.UUID
	// UpstreamProxyURL is the OPTIONAL corporate parent proxy the sidecar chains
	// egress through (http://[user:pass@]host[:port] — https-to-proxy is rejected
	// by the sidecar's own config validation, parseUpstreamProxy). Threaded
	// verbatim to the proxy sidecar; control-plane calls bypass it. Empty =>
	// direct dial.
	//
	// Sourced operator-wide at dispatch (internal/api/runs.go dispatchWithVerify)
	// from the persisted site-config's UpstreamProxySecretRef, resolved to the
	// secret's value. Any resolution failure (unset ref, missing secret, non-http
	// URL) leaves this "" rather than failing the run — see
	// resolveUpstreamProxyURL and its audit event run.upstream_proxy.resolve.
	UpstreamProxyURL string
}

type Resources

type Resources struct {
	CPUMillis int64
	MemoryMiB int64
	// PidsLimit caps the number of processes/threads in the sandbox — the
	// fork-bomb guard for the host PID space. Zero => driver default.
	PidsLimit int64
	// DiskMiB caps writable storage. Best-effort: the docker driver applies it
	// only when the daemon storage driver supports a per-container quota
	// (overlay2 with project quota, or btrfs/zfs); otherwise it warns and runs
	// uncapped rather than hard-failing the run.
	DiskMiB int64
}

Resources are the hard sandbox caps the driver applies as cgroup / storage limits. A ZERO field means "use the driver's conservative platform default" (the docker driver fills CPU/memory/PIDs unconditionally so EVERY sandbox is capped even when policy sets nothing). The control plane copies these from a policy's types.ResourceLimits at dispatch.

type Runner

type Runner interface {
	Name() string
	Capabilities(ctx context.Context) (Capabilities, error)
	// CreateSandbox provisions the sandbox AND its sidecars (proxy, recorder)
	// with L0 confinement: no default route, egress only via the proxy.
	CreateSandbox(ctx context.Context, spec SandboxSpec) (Sandbox, error)
	// Exec starts the agent process inside the sandbox (PTY attached when
	// recording). Returns when the process has been started, not finished. The
	// returned agentExecID identifies the started process for exec-based substrates
	// (the docker idle-container + `docker exec` path); it is "" for exec-less /
	// main-process substrates (krun), where the container IS the agent. Persist it
	// so the crash reconciler can observe agent liveness across a wardynd restart
	// via AgentStatus.
	Exec(ctx context.Context, ref string, argv []string) (agentExecID string, err error)
	// Wait blocks until the agent process started by Exec for this sandbox ref
	// has exited, returning its exit code. It is ONLY valid after a successful
	// Exec on the same ref (it observes the agent exec Exec created). Wait
	// honours ctx cancellation/deadline and returns an error if no agent exec
	// is tracked for ref (e.g. Exec was never called, or the ref is unknown).
	Wait(ctx context.Context, ref string) (exitCode int, err error)
	// Attach opens a NEW interactive exec (an interactive shell) inside the
	// already-RUNNING sandbox ref and returns a live PTY Session. This is the
	// foundation of interactive session mode: a human attaches to a live PTY in
	// a running sandbox. The exec is SEPARATE from the agent process Wait tracks
	// — it is a fresh shell, so attaching/detaching never affects the agent.
	//
	// Session.Close tears down ONLY the exec stream, NOT the sandbox: detaching
	// leaves the run and its sidecars exactly as they were. The interactive
	// shell is bounded by the SAME L0 egress + confinement envelope as the agent
	// (it runs inside the existing sandbox); Attach opens no new network path
	// (invariant 3). Callers MUST record the human principal for attribution
	// (invariant 4) at the call site (the runner is identity-agnostic).
	Attach(ctx context.Context, ref string, opts AttachOptions) (Session, error)
	// ExecStream launches spec.Argv inside the already-RUNNING sandbox ref as a
	// fresh, streamable exec, distinct from both the agent process Exec starts
	// (Wait/AgentStatus observe THAT one, not this) and from Attach's
	// interactive shell (no PTY tmux session, no shell wrapping — argv runs
	// directly). See ExecSpec/ExecSession for the streaming and TTY-merge
	// contract.
	//
	// UNLIKE Exec, ExecStream MUST be repeatable against the SAME ref: a
	// long-lived consumer (an SSH/SFTP bridge, a multiplexed shell) opens ONE
	// ExecStream per channel against ONE long-lived sandbox ref for the life
	// of the connection, so a substrate that errored or no-op'd on a second
	// call would break that consumer outright. A k8s substrate implements
	// ExecStream on the streaming exec subresource (pods/<name>/exec —
	// repeatable, leaves no pod-spec residue), NOT an ephemeral container
	// (add-only; that add-only limit is what makes Exec one-shot, not
	// ExecStream — see Substrate.Exec's doc).
	//
	// SECURITY: ExecStream opens no new network path (invariant 3). Callers
	// MUST record the human principal for attribution (invariant 4) at the
	// call site (the runner is identity-agnostic) — a raw argv makes this the
	// MORE dangerous of the two streaming primitives, not less.
	ExecStream(ctx context.Context, ref string, spec ExecSpec) (*ExecSession, error)
	Status(ctx context.Context, ref string) (Status, error)
	// AgentStatus reports the AGENT's observed state in a restart-safe way, given
	// the agentExecID Exec returned (persisted on the run row). For exec-based
	// substrates it inspects that exec, so a run whose agent has exited reports a
	// terminal State + ExitCode even while the idle container is still up — the
	// distinction container-level Status cannot make after a restart lost the
	// in-memory exec map. When agentExecID is "" (exec-less/main-process, or Exec
	// never ran) it falls back to Status, where the container IS the agent.
	AgentStatus(ctx context.Context, ref, agentExecID string) (Status, error)
	// StopSandbox is the graceful path (lifecycle auto-stop).
	StopSandbox(ctx context.Context, ref string) error
	// KillSandbox is the kill-switch path: immediate teardown. The control
	// plane cascades identity + credential revocation around this call.
	KillSandbox(ctx context.Context, ref string) error
}

Runner is the lifecycle contract. Implementations must be safe for concurrent use. Every method must be idempotent where the verb implies it (Stop/Kill on a gone sandbox return nil).

type Sandbox

type Sandbox struct {
	Ref    string // container ID / pod name
	Driver string
	// EnforcedClass is what the driver actually applied (>= requested or error).
	EnforcedClass types.ConfinementClass
}

Sandbox is a handle to a created sandbox.

type SandboxSpec

type SandboxSpec struct {
	RunID            uuid.UUID
	Image            string // resolved agent/workspace OCI image
	ConfinementClass types.ConfinementClass
	// Env is non-secret environment. Secrets NEVER pass through here —
	// they are injected proxy-side or resolved late via the broker.
	Env map[string]string
	// ProxyConfig wires the L0 path: the sandbox's only egress is the
	// wardyn-proxy sidecar identified here.
	ProxyConfig ProxyConfig
	// Resources are hard caps (cgroups / ResourceQuota).
	Resources Resources
	// Labels are attached to the sandbox for attestation selectors and audit.
	Labels map[string]string
	// Mounts are operator/policy-controlled host bind mounts into the sandbox
	// (e.g. a host repo at ~/work for the WSL-migration substrate / a persistent
	// workspace). SECURITY: these are POLICY-controlled, NEVER attacker-controlled.
	// The ONLY population path is internal/api dispatch copying a policy's
	// RunPolicySpec.WorkspaceMounts here; the create-run HTTP request body has no
	// mounts field, so a prompt-injected agent or a malicious run requester can
	// never choose a host mount. Drivers apply these as bind mounts AND enforce a
	// deny-list defense-in-depth (see runner/docker/driver.go) even though the
	// values came from policy. Default ReadOnly.
	Mounts []Mount
	// Interactive marks a run that comes up idle for `wardyn attach` (no task is
	// exec'd). Drivers use it to prepare the workspace on the idle main process —
	// e.g. clone the repo into ~/work — so the attach shell isn't empty. A non-
	// interactive run ignores it (its task exec does the preparation).
	Interactive bool
}

SandboxSpec is everything a driver needs to create one governed sandbox.

type Session

type Session interface {
	// Read copies terminal output (PTY bytes) into p. It returns io.EOF when the
	// shell exits or the stream is closed.
	Read(p []byte) (int, error)
	// Write sends keystrokes (PTY bytes) into the shell.
	Write(p []byte) (int, error)
	// Resize informs the PTY of a new window size (e.g. on a browser resize).
	Resize(ctx context.Context, cols, rows uint16) error
	// Close tears down ONLY the interactive exec stream. It does NOT stop the
	// sandbox, the agent process, or any sidecar — detaching a human leaves the
	// run exactly as it was.
	Close() error
}

Session is a live, bidirectional interactive PTY stream into a RUNNING sandbox, opened by Runner.Attach. It is the human-facing analogue of the agent exec: a person types into Write and reads the terminal back from Read.

SECURITY (invariant 3): the interactive shell runs INSIDE the existing sandbox, so it is bounded by exactly the same L0 structural-egress and confinement envelope as the agent process. Attach opens NO new network path — the stream flows control-plane -> dockerd -> container, never through the sandbox's HTTP_PROXY egress path, and egress/mint enforcement stays at the proxy/broker. Attach therefore grants a terminal, not a new egress route.

A Session is NOT safe for concurrent Read/Write from multiple goroutines on the same direction, but the typical pump runs Read in one goroutine and Write in another, which is supported.

type Status

type Status struct {
	State    types.RunState
	ExitCode *int
	Message  string
}

Status reports observed sandbox state.

Directories

Path Synopsis
Package orchestrator is the build-tag-free runner.Runner the control plane talks to.
Package orchestrator is the build-tag-free runner.Runner the control plane talks to.
Package substrate defines the confinement-substrate sub-interface: the seam beneath the runner.Runner surface that lets a non-OCI microVM VMM (SmolVM, Firecracker, …) back a Confinement Class alongside the OCI/Docker substrate, without the control plane (or each substrate) re-implementing the runner contract.
Package substrate defines the confinement-substrate sub-interface: the seam beneath the runner.Runner surface that lets a non-OCI microVM VMM (SmolVM, Firecracker, …) back a Confinement Class alongside the OCI/Docker substrate, without the control plane (or each substrate) re-implementing the runner contract.

Jump to

Keyboard shortcuts

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