sandbox

package
v0.0.0-...-c115430 Latest Latest
Warning

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

Go to latest
Published: May 16, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package sandbox implements the WASM connector runtime.

Per ADR-0005, every connector runs under an isolation boundary the runtime enforces. v1 targets WASI Preview 1 plus Aileron-specific host imports (`aileron_http_request`, `aileron_log`); the WASI Preview 2 / Component Model surface ratified by ADR-0005 lights up when Wazero supports it. The capability enforcement, resource limits, structured errors, and per-call lifecycle in this package satisfy issue #359's acceptance criteria without depending on P2.

The package exposes a small interface — Runtime and Connector — so the engine is swappable. The default implementation is Wazero; tests substitute fakes where they want to exercise the host-side policy independently.

Index

Constants

View Source
const (
	// DefaultMemoryBytes is the default per-instance memory cap.
	DefaultMemoryBytes uint64 = 64 * 1024 * 1024

	// MaxMemoryBytes is the hard ceiling — manifest requests above this
	// are clamped.
	MaxMemoryBytes uint64 = 1024 * 1024 * 1024

	// DefaultWallTime is the default per-call wall time.
	DefaultWallTime = 30 * time.Second

	// MaxWallTime is the hard ceiling for per-call overrides.
	MaxWallTime = 5 * time.Minute
)

ADR-0005 §"Resource limits are enforced and bounded" defaults and hard ceilings. The connector manifest may request a higher cap, up to the ceiling; the action manifest may override per-call wall time, up to the ceiling. Field-level overrides on the action manifest are not part of the v1 schema and are deferred (see plan).

View Source
const HelpTimeout = 5 * time.Second

HelpTimeout caps how long RunHelp waits for `<binary> --help` to produce output before killing the subprocess. Five seconds matches the existing unsandboxed runner the manual `aileron action wrap` flow uses (internal/wrap.HelpRunnerExec), so the introspection deadline is the same whether the caller asks for the sandboxed or unsandboxed variant.

Variables

View Source
var ErrSpawnUnavailable = errors.New("spawn: sandbox unavailable on this platform")

ErrSpawnUnavailable is returned by SpawnExecutor.Spawn when the running platform does not support the spawn primitive's enforcement requirements. The host function translates this into a structured spawn_sandbox_unavailable error class.

Functions

func SandboxAvailable

func SandboxAvailable() error

SandboxAvailable reports whether the spawn primitive's platform sandbox is usable on this host. Returns nil when the runtime can install the confinement layers ADR-0014 commits to (FS scoping, network egress denial, process scoping); returns a wrapped ErrSpawnUnavailable with an actionable remediation hint otherwise.

Intended for install-time use so a user installing a spawn- using connector on a host without kernel-level confinement gets a clear error before any binary is fetched or stored — per ADR-0014's "Graceful unavailability" section and #719's cross-platform checklist.

Implementation lives in build-tagged sibling files: each platform probes the OS-specific primitives the runtime would need at spawn time:

  • Linux: `unprivileged_userns_clone` sysctl.
  • macOS: presence of `/usr/bin/sandbox-exec`.
  • Windows: open + close a session handle on the Windows Filtering Platform engine (validates BFE is running and the daemon has the rights to install filters).
  • Other (BSD, illumos, etc.): the fallback returns ErrSpawnUnavailable unconditionally; ADR-0014 designates those platforms as deny-spawn.

Types

type Boundary

type Boundary string

Boundary identifies the layer that produced a failure (per ADR-0010). The sandbox always emits "sandbox" — the kernel/WASM-engine boundary — regardless of which class is raised. Action-boundary and connector-manifest-boundary denials live in their respective packages (`internal/action` and `internal/cstore`) and emit their own values.

const (
	// BoundarySandbox names the WASM sandbox layer.
	BoundarySandbox Boundary = "sandbox"
)

type Call

type Call struct {
	Op                 string
	Args               map[string]any
	Limits             Limits
	AllowedAuthority   []string
	CredentialResolver credential.Resolver

	// AllowedSpawnPrograms is the action manifest's declared subset of
	// programs the connector may invoke via aileron_host.spawn. Each
	// entry is an absolute (or ~/-anchored) program path matching the
	// connector manifest's [capabilities.spawn].programs declaration.
	// Empty means the action did not narrow the connector's grant; the
	// connector manifest's policy is the only spawn gate.
	AllowedSpawnPrograms []string
}

Call describes one connector operation invocation.

`Op` and `Args` correspond to the action manifest's `[[execute]]` step fields (per ADR-0003) — the executor builds a Call from each step and hands it to the corresponding connector. `Limits` overrides the per-runtime defaults; the zero value uses ADR-0005 defaults (64 MiB / 30 s).

`AllowedAuthority` is the invoking action's declared subset of the connector's capabilities; the network host import checks this in addition to the connector manifest's grant (ADR-0003 §"defense in depth"). When empty, only the connector manifest gates network access.

`CredentialResolver` is the per-invocation handle the host uses to resolve a bound credential when the connector emits an outbound request that references its declared `[capabilities.credential]` (per ADR-0005 credential mediation). Nil means no binding is wired for this call; the host returns `binding_required` if the connector tries to use the credential path. Resolver lifetime is the single Invoke; per-call hostState drops the reference on completion so the connector cannot stash it across calls.

type Connector

type Connector interface {
	// Invoke runs `call` in a freshly-instantiated sandbox. Returns a
	// structured *Error on capability denial or resource-limit
	// termination per ADR-0010.
	Invoke(ctx context.Context, call Call) (Result, error)

	// Close releases the compiled-module cache. Outstanding Invoke
	// calls may complete; calling Invoke after Close is a programming
	// error.
	Close(ctx context.Context) error
}

Connector is a compiled, ready-to-instantiate connector binary. Each [Connector.Invoke] call creates a fresh sandbox instance, runs the call, and tears it down — per-invocation isolation per ADR-0005's "Connector instances are scoped to a single invocation" requirement.

type Error

type Error struct {
	Class     FailureClass
	Message   string
	Boundary  Boundary
	Retriable bool
	Details   map[string]any
}

Error is a structured sandbox-layer error per ADR-0010. The `Details` map carries class-specific structured context (e.g. denied host:port for capability_denied; named limit for resource_limit_exceeded).

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type FailureClass

type FailureClass string

FailureClass is the closed-set classification of sandbox failures. The taxonomy is the subset of ADR-0010's closed set produced at the sandbox boundary; new classes require an ADR amendment.

const (
	// ClassCapabilityDenied is the canonical class for any operation
	// the connector attempted that exceeds its declared grants. The
	// `Boundary` field disambiguates whether the WASM sandbox itself,
	// the connector manifest, or the action manifest produced the
	// denial — see ADR-0010 §"capable_denied" for the full taxonomy.
	ClassCapabilityDenied FailureClass = "capability_denied"

	// ClassResourceLimitExceeded is the canonical class for a sandbox
	// instance that hit a memory, wall-time, or fuel limit. The
	// `Limit` detail names which one.
	ClassResourceLimitExceeded FailureClass = "resource_limit_exceeded"

	// ClassConnectorRuntimeError is the canonical class for a connector
	// that failed inside its own code path (panicked, returned a
	// non-zero status, produced a malformed output payload).
	ClassConnectorRuntimeError FailureClass = "connector_runtime_error"

	// ClassConnectorLoadFailed is a hard fail at compile or
	// instantiation time: the WASM module is malformed, refuses to
	// instantiate, or imports a host function the runtime did not
	// register. Per ADR-0005 §"WASI host imports are gated by the
	// connector's `[capabilities.runtime]` declaration", out-of-grant
	// imports refuse at instantiation.
	ClassConnectorLoadFailed FailureClass = "connector_load_failed"

	// ClassBindingRequired is the canonical class for an outbound
	// request that referenced a credential capability the action did
	// not bind. The runtime mediates every credential use per ADR-0005;
	// when the connector emits an `http_request` with a `credential`
	// field but the action has no `[[bindings]]` entry that points at a
	// vault path with the expected kind, the host returns this class.
	// Mirrors `failure.BindingRequired` in the executor's surface.
	ClassBindingRequired FailureClass = "binding_required"
)

type HTTPDoer

type HTTPDoer interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPDoer is the narrow HTTP-client surface the sandbox needs. It matches `*http.Client.Do`'s signature so tests can substitute an in-memory client without HTTP. The runtime injects a Doer per invocation; default is a stock `*http.Client`.

type HostPolicy

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

HostPolicy enforces the connector manifest's `[capabilities.network]` declaration: outbound calls to undeclared hosts are denied at the syscall layer per ADR-0005 acceptance criterion #3.

Hosts are pinned `host:port` pairs (per ADR-0002 §"network access"); no wildcards are honored. The match is exact on both the host and the port.

func NewHostPolicy

func NewHostPolicy(m *cstore.Manifest) *HostPolicy

NewHostPolicy builds a HostPolicy from a connector manifest. A nil or missing `[capabilities.network]` block produces a deny-all policy — the connector did not declare network access and may not dial out.

func (*HostPolicy) AllowedHosts

func (p *HostPolicy) AllowedHosts() []string

AllowedHosts returns a sorted-stable copy of the policy's grant for auditing/error-detail purposes.

func (*HostPolicy) CheckHostPort

func (p *HostPolicy) CheckHostPort(hostPort string) error

CheckHostPort verifies a `host:port` string is in the allowed set. Mirrors HostPolicy.CheckURL's contract for the spawn-proxy path, where the proxy receives a host:port directly from the CONNECT request and does not have a full URL to parse.

Returns a structured `*Error` of class `capability_denied` and boundary `sandbox` when the call is refused.

func (*HostPolicy) CheckURL

func (p *HostPolicy) CheckURL(rawURL string) error

CheckURL parses `rawURL` and verifies its host:port is in the allowed set. Returns a structured *Error of class `capability_denied` and boundary `sandbox` when the call is refused.

HTTPS URLs default to port 443; HTTP URLs default to port 80; URLs with explicit ports use those. Schemes other than http/https are rejected — the connector ABI exposes HTTP only in v1, and any other scheme is a misuse of the host import.

type LimitKind

type LimitKind string

LimitKind names which resource limit was exceeded. Used in the `Details["limit"]` field of a Error when [Error.Class] is ClassResourceLimitExceeded (per ADR-0010's "specific limit named" requirement).

const (
	LimitMemory   LimitKind = "memory"
	LimitWallTime LimitKind = "wall_time"
)

type Limits

type Limits struct {
	MemoryBytes uint64
	WallTime    time.Duration
}

Limits configure resource ceilings per ADR-0005's "Resource limits are enforced and bounded" table. The zero value means "use ADR defaults".

MemoryBytes default is 64 MiB; the connector manifest may request a higher cap up to a hard ceiling of 1 GiB. WallTime default is 30 s, per-call override capped at 5 minutes.

func (Limits) MemoryPages

func (l Limits) MemoryPages() uint32

MemoryPages converts MemoryBytes to WebAssembly memory pages (64 KiB each), rounding *up* so the cap is never less than what the caller requested. Wazero's memory-limit API works in pages, not bytes.

func (Limits) Resolve

func (l Limits) Resolve() Limits

Resolve takes the caller-supplied limits, the connector manifest's declared overrides (if any), and produces the effective per-call limits. Zero values fall back to ADR defaults; values above the hard ceilings are clamped.

Precedence (highest wins):

  1. Caller's explicit non-zero limit (e.g. action manifest override).
  2. Connector manifest's declared cap.
  3. ADR-0005 default.

func (Limits) String

func (l Limits) String() string

String returns a one-line human-readable summary, used in error messages and structured logging.

type LogLine

type LogLine struct {
	Level   string
	Message string
}

LogLine is a single emission from the connector's `aileron_log` host import.

type ProxyEndpoint

type ProxyEndpoint struct {
	// TCPAddr is the host:port the proxy is listening on for
	// non-Linux platforms. Empty on Linux.
	TCPAddr string

	// UDSPath is the host filesystem path of the Unix-domain
	// socket the proxy is listening on for Linux. Empty on
	// non-Linux platforms.
	UDSPath string
}

ProxyEndpoint describes where a per-spawn CONNECT proxy is listening. macOS and Windows ([SBPL] / [WFP]) permit the wrapped CLI to reach a TCP loopback port directly, so [TCPAddr] is the `HTTPS_PROXY` value the runtime sets on the subprocess. Linux puts the wrapped CLI in a new network namespace under `CLONE_NEWNET`, where the host's TCP loopback is unreachable; the proxy binds on a Unix-domain socket whose path is in [UDSPath], and the in-namespace spawn helper bridges TCP from inside the namespace to that socket.

Exactly one of TCPAddr or UDSPath is populated for a given platform. The runtime's [processSpawn] selects which to set on the wrapped CLI's environment vs. on the helper request based on which field is non-empty.

type Result

type Result struct {
	Output map[string]any
	Logs   []LogLine
}

Result is what the sandbox produced. Successful runs surface their `Output` map; the executor encodes it into the tool-result content the LLM observes. `Logs` is the connector's structured log emissions during the call (via `aileron_log`).

type RunHelpResult

type RunHelpResult struct {
	Stdout   []byte
	Stderr   []byte
	ExitCode int
}

RunHelpResult is the captured outcome of a single sandboxed `<binary> --help` invocation. Both Stdout and Stderr may be populated regardless of ExitCode; many CLIs write usage to stderr while still exiting non-zero (notably `curl`), and callers parse whichever stream contains the help text.

func RunHelp

func RunHelp(ctx context.Context, programPath, cwd string, subcommandPath ...string) (RunHelpResult, error)

RunHelp invokes `<programPath> --help` under the platform spawn sandbox the runtime uses for declared connector actions. Confinement matches what would apply to a hub-published connector calling spawn at runtime:

  • fs_read scoped to `cwd` only (the directory the user invoked the CLI from). No `$HOME`, no user-config dirs.
  • fs_write denied entirely.
  • network egress denied entirely (no [capabilities.network] declared, so the sandbox engages without a proxy port).
  • HelpTimeout (5s) deadline; the subprocess is killed if it hasn't exited by then.
  • stdout / stderr capped to cstore.DefaultMaxStdoutBytes and cstore.DefaultMaxStderrBytes; output beyond the cap is dropped with a structured truncation marker per ADR-0014.

`subcommandPath` is the verb chain prepended before `--help`. Nil/empty yields `<programPath> --help` (the root-help call); `["issues", "create"]` yields `<programPath> issues create --help` so the introspector can walk Cobra-style nested verb trees. Each call is its own sandboxed invocation — callers running many paths in sequence pay the per-call sandbox-startup cost N times.

`programPath` must be absolute. RunHelp returns the ErrSpawnUnavailable wrapper from SandboxAvailable when the host can't honor the confinement the docstring promises — callers should treat that as a hard install-time failure rather than fall back to unsandboxed exec.

Intended caller: the `aileron cli add` introspector (issue #749), which parses the captured stdout via internal/wrap's `--help` parser. Generalizable to any other one-shot sandboxed invocation that doesn't need a fully-formed connector manifest on disk first.

type Runtime

type Runtime interface {
	// Compile prepares a connector for invocation. Compilation is
	// typically expensive (Wazero parses, validates, and ahead-of-time
	// translates the WASM module); instances produced from the compiled
	// form are cheap.
	//
	// `manifest` is the connector's parsed `manifest.toml` — the runtime
	// uses its `[capabilities.network]` and resource declarations to
	// gate calls and configure limits per ADR-0002 / ADR-0005.
	// `binary` is the raw `.wasm` bytes from the content-addressed
	// store.
	Compile(ctx context.Context, manifest *cstore.Manifest, binary []byte) (Connector, error)

	// Close releases the runtime and any compiled connectors. Calling
	// Compile after Close is a programming error.
	Close(ctx context.Context) error
}

Runtime is the engine that compiles connector binaries and produces invokable Connector handles. A Runtime is goroutine-safe; multiple Connectors compiled from a single Runtime may be invoked in parallel.

type RuntimeOption

type RuntimeOption func(*WazeroRuntime)

RuntimeOption configures a WazeroRuntime at construction.

func WithHTTPDoer

func WithHTTPDoer(d HTTPDoer) RuntimeOption

WithHTTPDoer overrides the HTTP client the sandbox uses for `aileron_host.http_request`. Defaults to http.DefaultClient; tests substitute fake doers to exercise the host policy without network.

func WithLogger

func WithLogger(log *slog.Logger) RuntimeOption

WithLogger sets the slog.Logger used to emit connector log lines and runtime diagnostics.

func WithSpawnExecutor

func WithSpawnExecutor(e SpawnExecutor) RuntimeOption

WithSpawnExecutor overrides the executor the runtime uses for `aileron_host.spawn`. Defaults to the production defaultSpawnExecutor; tests substitute fake executors to exercise the gate and the host-function plumbing without forking real processes.

type SpawnEnvelope

type SpawnEnvelope struct {
	Program           string            `json:"program"`
	Argv              []string          `json:"argv"`
	Env               map[string]string `json:"env,omitempty"`
	Cwd               string            `json:"cwd,omitempty"`
	Stdin             string            `json:"stdin,omitempty"`
	CredentialEnvKeys []string          `json:"credential_env_keys,omitempty"`
}

SpawnEnvelope is the JSON shape connectors marshal as input to `aileron_host.spawn`. Designed to be ergonomic across language ABIs; the runtime parses it host-side.

Fields:

  • Program: absolute path or ~/-anchored path of the binary to invoke.
  • Argv: the full argument vector starting with argv[0]. The runtime compares the argv (after placeholder elision) against the manifest's argv_patterns.
  • Env: the environment to set on the subprocess. Each key must be in the manifest's env_passthrough; values are caller-supplied except for keys in CredentialEnvKeys, which the runtime resolves and injects.
  • Cwd: optional working directory. Must match the manifest's cwd (when set) and lie within fs_read.
  • Stdin: optional bytes piped to the subprocess on stdin.
  • CredentialEnvKeys: optional list of env keys whose values the runtime should resolve from the connector's bound credential and inject. The connector never holds the credential bytes.

type SpawnExecutor

type SpawnExecutor interface {
	Spawn(ctx context.Context, env SpawnEnvelope, limits SpawnLimits) (SpawnResult, error)
}

SpawnExecutor is the narrow surface the host functions use to actually invoke a subprocess. Tests substitute fakes; the production implementation forks an os/exec.Cmd with the platform's sandbox applied (per ADR-0014).

The executor is invoked only after CheckSpawn has approved. It is responsible for the second-line enforcement (FS scoping, network denial, process scoping) on the platforms it supports, and for honoring the runtime-supplied SpawnLimits when capturing the subprocess's stdout and stderr. On unsupported platforms it returns ErrSpawnUnavailable so the host function emits a structured spawn_sandbox_unavailable error.

type SpawnLimits

type SpawnLimits struct {
	// MaxStdoutBytes caps the bytes the executor returns in
	// [SpawnResult.Stdout]. Output past this point is dropped and
	// a structured truncation marker is appended to the captured
	// bytes.
	MaxStdoutBytes int64

	// MaxStderrBytes caps the bytes the executor returns in
	// [SpawnResult.Stderr]. Same truncation semantics as
	// MaxStdoutBytes.
	MaxStderrBytes int64

	// FSRead is the manifest's declared read-scope (per
	// [cstore.ManifestSpawn.FSRead]). The platform sandbox
	// translates these into kernel-enforced filesystem confinement
	// (Linux Landlock + mount namespace, macOS SBPL `file-read*`
	// rules, Windows ACL adjustments).
	FSRead []string

	// FSWrite is the manifest's declared write-scope (per
	// [cstore.ManifestSpawn.FSWrite]). Same role as FSRead but
	// for writes.
	FSWrite []string

	// ProxyAddr is the per-invocation proxy endpoint the platform
	// sandbox permits loopback access to. Empty when the connector
	// did not declare `[capabilities.network]` or when the proxy
	// is reachable only via [ProxyUDSPath] (Linux + helper bridge).
	// Format is `host:port`, typically `127.0.0.1:<ephemeral>`.
	// macOS reads this for the SBPL `(allow network*)` rule;
	// Windows for the WFP filter.
	ProxyAddr string

	// ProxyUDSPath is the host-filesystem path of a Unix-domain
	// socket the per-invocation CONNECT proxy is listening on.
	// Linux populates this when the wrapped CLI runs inside a
	// `CLONE_NEWNET` namespace where the host's TCP loopback is
	// unreachable; the in-namespace spawn helper bridges from a
	// namespace-local TCP loopback to this socket via
	// [RunSpawnShim]. Empty on macOS, Windows, and on Linux for
	// connectors that don't trigger the helper rewire.
	ProxyUDSPath string
}

SpawnLimits carries the runtime-decided per-invocation parameters the executor needs in addition to the connector-supplied envelope. Originally just byte caps for captured output, the struct now carries the resolved filesystem scopes and proxy endpoint the platform sandbox uses to construct its per-OS confinement (per ADR-0014's "Network confinement: daemon-mediated proxy" and the per-platform sandbox sections).

The runtime resolves every field from the manifest plus per-spawn state (proxy port) before calling the executor. Connectors never set these directly.

func (SpawnLimits) PlatformSandboxRequested

func (l SpawnLimits) PlatformSandboxRequested() bool

PlatformSandboxRequested reports whether the manifest carried any platform-sandbox-relevant declaration (FS scope, network proxy). Used by each platform's [applyPlatformSandbox] to decide whether to engage the OS-level confinement. A zero-value SpawnLimits (legacy spawn with no manifest scopes) returns false so tests and pre-sandbox callers keep running unchanged.

type SpawnPolicy

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

SpawnPolicy enforces a connector manifest's `[capabilities.spawn]` declaration (per ADR-0002 spawn primitive, ADR-0014 sandbox tech). Built once per connector and consulted on every spawn host-function call before any process is created.

SpawnPolicy is the gate; the platform sandbox in `spawnExecutor` is the second-line enforcement. Both must agree for a call to proceed.

The policy also carries the per-stream output caps the runtime applies to subprocess stdout and stderr (per cstore.ManifestSpawnLimits and ADR-0014's output-cap consequences). The caps are derived once from the manifest at policy construction and consulted on every spawn invocation.

func NewSpawnPolicy

func NewSpawnPolicy(m *cstore.Manifest) *SpawnPolicy

NewSpawnPolicy builds a SpawnPolicy from a connector manifest. A nil or absent `[capabilities.spawn]` block produces a deny-all policy: the connector did not declare spawn and may not invoke a subprocess.

func (*SpawnPolicy) BuildEnvelopeFromOp

func (p *SpawnPolicy) BuildEnvelopeFromOp(opName string, args map[string]string, getEnv func(string) string) (SpawnEnvelope, *Error)

BuildEnvelopeFromOp resolves `opName` against the manifest's [capabilities.spawn.operations] table, substitutes `{name}` placeholders in the operation's argv pattern from `args`, and returns a fully-formed SpawnEnvelope ready for CheckSpawn / processSpawn.

`getEnv` resolves the values for the manifest's env_passthrough keys from a source the caller chooses (e.g. os.Getenv in production; a fake in tests). Empty values are omitted from the envelope.

Returns a structured *Error of class capability_denied when:

  • The op name is not declared in the manifest's operations table (boundary_detail: connector_manifest).
  • A placeholder in the operation's argv has no matching arg (boundary_detail: envelope, set by argvPattern.Substitute).
  • The policy has no primary program (no programs declared).

func (*SpawnPolicy) CheckSpawn

func (p *SpawnPolicy) CheckSpawn(env SpawnEnvelope) error

CheckSpawn validates an incoming SpawnEnvelope against the connector manifest's [capabilities.spawn] declaration. Returns a structured *Error of class capability_denied (boundary=sandbox) when the envelope falls outside the grant.

The check is the first gate; the action-boundary subset (when supplied) and the platform sandbox apply additional enforcement. Mirrors HostPolicy.CheckURL's role for network capabilities.

func (*SpawnPolicy) StderrCap

func (p *SpawnPolicy) StderrCap() int64

StderrCap returns the resolved stderr byte cap.

func (*SpawnPolicy) StdoutCap

func (p *SpawnPolicy) StdoutCap() int64

StdoutCap returns the resolved stdout byte cap. Always positive when the policy was constructed from a valid manifest.

type SpawnProxy

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

SpawnProxy is the per-invocation HTTP CONNECT proxy that mediates a spawned subprocess's outbound network. The proxy is the single enforcement seam for `[capabilities.network]` on spawn connectors (per ADR-0014's "Network confinement: daemon-mediated proxy" section). One proxy is created per spawn call, served on a local-only listener the runtime hands in (TCP loopback on macOS/Windows; Unix-domain socket on Linux), then closed when the spawn completes.

The proxy operates at the CONNECT method only. It does not terminate TLS, so it sees host:port plus tunneled bytes but never the request URL, headers, or body. Allowlist evaluation reuses HostPolicy from the WASM network gate; both gates check against the same `host:port` set.

func NewSpawnProxy

func NewSpawnProxy(policy *HostPolicy, logger *slog.Logger, fqn string) *SpawnProxy

NewSpawnProxy constructs a proxy that enforces `policy` and emits audit attributes naming `fqn`. The proxy is not started until SpawnProxy.Serve is called. `logger` is optional; nil suppresses audit emission (useful in tests).

func (*SpawnProxy) Close

func (p *SpawnProxy) Close() error

Close shuts down the proxy. Outstanding CONNECTs are allowed to finish; new accepts unblock with net.ErrClosed. Safe to call from any goroutine. Idempotent.

func (*SpawnProxy) Serve

func (p *SpawnProxy) Serve(ctx context.Context, ln net.Listener) error

Serve accepts connections on `ln` until the listener returns net.ErrClosed (typically via SpawnProxy.Close). Each accepted connection is handled in its own goroutine. Serve blocks until the listener is closed and all in-flight CONNECTs have drained.

Returns nil on a clean shutdown via Close; returns the underlying accept error otherwise.

func (*SpawnProxy) SetDialer

func (p *SpawnProxy) SetDialer(dial func(ctx context.Context, network, addr string) (net.Conn, error))

SetDialer overrides the function the proxy uses to reach upstream hosts. The runtime never calls this; tests inject fakes to avoid dialing real network endpoints. Must be called before [Serve].

type SpawnResult

type SpawnResult struct {
	ExitCode int
	Stdout   []byte
	Stderr   []byte
}

SpawnResult is the captured outcome of a single subprocess invocation returned to the connector through the host-function read path.

type WazeroRuntime

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

WazeroRuntime is the default Runtime implementation, backed by Wazero (the only viable pure-Go WASM engine in v1; see the plan file at /Users/alr/.claude/plans/are-there-alternatives-to-stateless-starfish.md).

One WazeroRuntime supports many [Connector]s, instantiated lazily as connectors are compiled. Each Connector compiles its WASM bytes once and caches the result; per-call invocation creates a fresh module instance from the cached compilation, runs `_start`, and tears the instance down — the per-invocation isolation required by ADR-0005's acceptance criterion #8.

func NewWazeroRuntime

func NewWazeroRuntime(ctx context.Context, opts ...RuntimeOption) (*WazeroRuntime, error)

NewWazeroRuntime builds a Wazero-backed runtime with the supplied options. Callers must Close() the returned runtime when finished.

func (*WazeroRuntime) Close

func (w *WazeroRuntime) Close(ctx context.Context) error

Close releases the underlying Wazero runtime. Outstanding Compile/Invoke calls return errors after Close.

func (*WazeroRuntime) Compile

func (w *WazeroRuntime) Compile(ctx context.Context, manifest *cstore.Manifest, binary []byte) (Connector, error)

Compile implements Runtime.

Directories

Path Synopsis
Package forwarder embeds the shared spawn-forwarder WASM into the Aileron daemon binary.
Package forwarder embeds the shared spawn-forwarder WASM into the Aileron daemon binary.
Package sandboxtest provides reusable test helpers for the spawn primitive (per ADR-0002, ADR-0014).
Package sandboxtest provides reusable test helpers for the spawn primitive (per ADR-0002, ADR-0014).
Package spawnhelper defines the post-namespace setup step that runs inside the spawn-sandbox namespace before the wrapped CLI exec's.
Package spawnhelper defines the post-namespace setup step that runs inside the spawn-sandbox namespace before the wrapped CLI exec's.

Jump to

Keyboard shortcuts

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