os

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package os provides OS-level kernel sandbox primitives for tool execution. On macOS, it uses Seatbelt (sandbox-exec). Linux isolation is planned but not yet enforced.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrIsolatorUnavailable indicates the requested OS sandbox is not available on this platform.
	ErrIsolatorUnavailable = errors.New("OS sandbox isolator unavailable")

	// ErrSandboxRequired indicates that sandbox.failClosed is true and no OS sandbox is available.
	ErrSandboxRequired = errors.New("sandbox required but OS isolator unavailable")

	// ErrInvalidPolicy indicates a policy configuration error.
	ErrInvalidPolicy = errors.New("invalid sandbox policy")
)

Functions

func CleanupProfileFile

func CleanupProfileFile(_ *exec.Cmd)

CleanupProfileFile is a no-op on non-macOS.

func GenerateSeatbeltProfile

func GenerateSeatbeltProfile(policy Policy) (string, error)

GenerateSeatbeltProfile creates a macOS Seatbelt .sb profile string from a Policy. All paths are resolved to absolute paths and validated against injection.

func SelectBackend

func SelectBackend(mode BackendMode, candidates []BackendCandidate) (OSIsolator, BackendInfo)

SelectBackend picks the appropriate isolator from candidates based on the requested mode.

BackendAuto: returns the first available candidate. If none available, returns a noopIsolator. BackendNone: always returns a noopIsolator. Explicit mode (seatbelt/bwrap/native): returns the matching candidate's isolator as-is, even if unavailable. If the mode is not found in candidates, returns a noopIsolator.

Types

type BackendCandidate

type BackendCandidate struct {
	Mode     BackendMode
	Isolator OSIsolator
}

BackendCandidate pairs a backend mode with its isolator implementation.

func PlatformBackendCandidates

func PlatformBackendCandidates() []BackendCandidate

PlatformBackendCandidates returns the candidate list for the current platform. macOS: seatbelt, bwrap (Linux-only stub on darwin), native (stub). Linux: bwrap (real isolator if bubblewrap installed, otherwise unavailable), native (stub). Other: empty (will fallback to noop via SelectBackend).

type BackendInfo

type BackendInfo struct {
	Name      string
	Mode      BackendMode
	Available bool
	Reason    string
}

BackendInfo describes the status of a single sandbox backend.

func ListBackends

func ListBackends(candidates []BackendCandidate) []BackendInfo

ListBackends returns status information for each candidate backend.

type BackendMode

type BackendMode int

BackendMode identifies a sandbox backend by type.

const (
	// BackendAuto selects the first available backend automatically.
	BackendAuto BackendMode = iota
	// BackendSeatbelt selects the macOS Seatbelt (sandbox-exec) backend.
	BackendSeatbelt
	// BackendBwrap selects the Linux bubblewrap (bwrap) backend.
	BackendBwrap
	// BackendNative selects the native kernel sandbox (landlock+seccomp) backend.
	BackendNative
	// BackendNone disables all sandbox backends explicitly.
	BackendNone
)

func ParseBackendMode

func ParseBackendMode(s string) (BackendMode, error)

ParseBackendMode converts a string to a BackendMode. An empty string maps to BackendAuto.

func (BackendMode) String

func (m BackendMode) String() string

String returns a human-readable label for the backend mode.

type BwrapIsolator

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

BwrapIsolator wraps exec.Cmd with bubblewrap (bwrap) on Linux to provide process-level isolation: filesystem bind mounts (read-only root + writable workspace), network unshare, and PID/IPC/UTS/cgroup namespaces.

The absolute bwrap path is captured at probe time (NewBwrapIsolator) and reused at Apply time so that PATH or working-directory changes between probe and execution cannot redirect to a different binary.

Availability is determined by a two-phase smoke probe:

  1. Base probe — reuses compileBwrapArgs with a read-only-global + NetworkAllow policy (matching MCPServerPolicy's network model) to verify the pid/ipc/uts/cgroup namespaces and root ro-bind that every lango policy requires. Failure marks the whole isolator unavailable.
  2. Network probe — additionally runs a NetworkDeny policy through compileBwrapArgs to verify --unshare-net works. Failure does NOT mark the isolator unavailable; instead, it downgrades Apply() so that only NetworkDeny/NetworkUnixOnly policies are rejected, while NetworkAllow policies (e.g. MCPServerPolicy) still succeed.

This two-phase design keeps the probe argv in lockstep with the runtime argv (zero drift because both go through compileBwrapArgs) while preventing a host that blocks --unshare-net from false-negating MCP.

func (*BwrapIsolator) Apply

func (b *BwrapIsolator) Apply(_ context.Context, cmd *exec.Cmd, policy Policy) error

Apply rewrites cmd to run inside bwrap with arguments compiled from policy. The original argv becomes the child program after the bwrap "--" separator.

Apply-time network gate: if the probe could not verify --unshare-net, the isolator rejects NetworkDeny/NetworkUnixOnly policies with an ErrIsolatorUnavailable-wrapped error BEFORE mutating cmd. NetworkAllow policies proceed normally — partial degradation does not affect consumers that do not require network isolation.

func (*BwrapIsolator) Available

func (b *BwrapIsolator) Available() bool

func (*BwrapIsolator) Name

func (b *BwrapIsolator) Name() string

func (*BwrapIsolator) NetworkIsolationAvailable

func (b *BwrapIsolator) NetworkIsolationAvailable() bool

NetworkIsolationAvailable reports whether the network smoke probe verified that --unshare-net works on this host. When false, Apply() rejects NetworkDeny/NetworkUnixOnly policies. NetworkAllow policies are unaffected. Available()==true does not imply network isolation availability; consumers that require network isolation should check this method separately.

func (*BwrapIsolator) NetworkIsolationReason

func (b *BwrapIsolator) NetworkIsolationReason() string

NetworkIsolationReason returns the diagnostic message when network isolation is unavailable. Empty when NetworkIsolationAvailable()==true. The primary Reason() method (from the OSIsolator interface) remains empty whenever Available()==true, preserving the existing spec contract that partial degradation is surfaced only through the dedicated network methods.

func (*BwrapIsolator) Reason

func (b *BwrapIsolator) Reason() string

func (*BwrapIsolator) Version

func (b *BwrapIsolator) Version() string

Version returns the captured `bwrap --version` string, or "" when the isolator is unavailable. CLI surfaces (e.g. `lango sandbox test`) can type-assert to a versioner interface to display this.

type FilesystemPolicy

type FilesystemPolicy struct {
	// ReadOnlyGlobal allows read access to the entire filesystem.
	// When true, ReadPaths is ignored — all paths are readable.
	ReadOnlyGlobal bool

	// ReadPaths are paths allowed for reading (used when ReadOnlyGlobal is false).
	ReadPaths []string

	// WritePaths are paths allowed for writing.
	WritePaths []string

	// DenyPaths are paths explicitly denied (takes precedence over allow).
	DenyPaths []string
}

FilesystemPolicy defines filesystem access rules.

type NetworkPolicy

type NetworkPolicy string

NetworkPolicy controls network access from the sandbox.

const (
	// NetworkDeny blocks all network access.
	NetworkDeny NetworkPolicy = "deny"

	// NetworkAllow permits unrestricted network access.
	NetworkAllow NetworkPolicy = "allow"

	// NetworkUnixOnly allows only AF_UNIX sockets (for local IPC/proxy).
	NetworkUnixOnly NetworkPolicy = "unix-only"
)

type OSIsolator

type OSIsolator interface {
	// Apply configures the given exec.Cmd to run under OS-level isolation.
	// The command may be wrapped (e.g., sandbox-exec on macOS).
	// Apply does not start the process.
	Apply(ctx context.Context, cmd *exec.Cmd, policy Policy) error

	// Available reports whether this isolator's OS primitives are functional.
	Available() bool

	// Name returns the isolator name (e.g., "seatbelt", "noop").
	Name() string

	// Reason returns a human-readable explanation of why the isolator is
	// unavailable. Returns "" when Available() is true.
	Reason() string
}

OSIsolator applies OS-level security restrictions to a subprocess command. It modifies exec.Cmd in place before the caller runs it. On macOS: wraps with sandbox-exec and a generated Seatbelt profile. On Linux: wraps with bubblewrap (bwrap) when the binary is installed (BwrapIsolator). The native Landlock+seccomp backend is not yet implemented; selecting backend=native or relying on the default noop path leaves the command unsandboxed.

func NewBwrapIsolator

func NewBwrapIsolator() OSIsolator

NewBwrapIsolator probes the system for bwrap and returns an isolator that either reports availability with a captured absolute path + version, or is unavailable with a human-readable reason. See the BwrapIsolator doc comment for the two-phase probe contract.

func NewCompositeIsolator

func NewCompositeIsolator(isolators ...OSIsolator) OSIsolator

NewCompositeIsolator returns an isolator that applies multiple isolators in sequence.

func NewNativeStub

func NewNativeStub() OSIsolator

NewNativeStub returns a stub isolator for the native kernel sandbox backend.

func NewOSIsolator

func NewOSIsolator() OSIsolator

NewOSIsolator returns the legacy "default" isolator for the current platform. It is kept for backwards compatibility with callers that have not yet migrated to the backend registry.

On macOS: SeatbeltIsolator. On Linux: noopIsolator pointing to backend=bwrap (the native Landlock+ seccomp backend is not yet implemented; bwrap is wired via the registry, not via this function). On unsupported platforms: noopIsolator.

New code should use ParseBackendMode + SelectBackend(mode, PlatformBackendCandidates()) instead so that backend selection (auto, seatbelt, bwrap, native, none) is honored.

type PlatformCapabilities

type PlatformCapabilities struct {
	// HasSeatbelt indicates macOS sandbox-exec is available.
	HasSeatbelt bool
	// SeatbeltReason explains the Seatbelt probe result (e.g., "sandbox-exec found", "not on darwin").
	SeatbeltReason string

	// HasLandlock indicates Linux Landlock LSM is available (kernel 5.13+).
	// Detected by calling landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION).
	HasLandlock bool
	// LandlockABI is the detected Landlock ABI version (0 = unavailable, 1+ = supported).
	LandlockABI int
	// LandlockReason explains the Landlock probe result (e.g., "Landlock ABI 3",
	// "Landlock not supported by this kernel (requires 5.13+)").
	LandlockReason string

	// HasSeccomp indicates that the kernel exposes the seccomp prctl interface
	// (PR_GET_SECCOMP succeeds). This does NOT prove that BPF filters can be
	// installed — it is a generic presence signal only. The SeccompReason field
	// carries the qualified description.
	HasSeccomp bool
	// SeccompReason explains the seccomp probe result, including the caveat
	// that PR_GET_SECCOMP success only proves interface presence, not filter
	// capability.
	SeccompReason string

	// Platform is the runtime GOOS value.
	Platform string

	// KernelVersion is the OS kernel version string.
	KernelVersion string
}

PlatformCapabilities describes what OS-level sandbox primitives are available.

func Probe

func Probe() PlatformCapabilities

Probe detects the available OS-level sandbox capabilities.

func (PlatformCapabilities) Summary

func (c PlatformCapabilities) Summary() string

Summary returns a human-readable summary of the detected capabilities.

type Policy

type Policy struct {
	// Filesystem controls file access.
	Filesystem FilesystemPolicy

	// Network controls network access.
	Network NetworkPolicy

	// Process controls process creation and signals.
	Process ProcessPolicy

	// AllowedNetworkIPs are IP addresses allowed for outbound connections (macOS only).
	// On Linux, this field is ignored (seccomp cannot filter by IP).
	AllowedNetworkIPs []string
}

Policy defines the sandbox restrictions for a single tool execution.

func DefaultToolPolicy

func DefaultToolPolicy(workDir, dataRoot string) Policy

DefaultToolPolicy returns the standard sandbox policy for local tool execution. Read-global, write-workspace+/tmp, no network. The first ancestor git metadata discovered via upward walk from workDir and the entire lango control-plane (dataRoot, e.g. ~/.lango) are denied so that sandboxed children cannot read or write the agent's own state, secrets, session database, skills directory, or other internal data.

Git metadata discovery uses findGitRoot's two-path model: a standard `.git` directory adds one DenyPaths entry; a linked worktree `.git` file adds two — the pointer file itself (denied at file level via PR 5c) AND the resolved gitdir target it points to. A fully non-repo workspace or a worktree pointer whose content is malformed silently degrades to whatever coverage is possible. A missing dataRoot is also skipped; pass an empty dataRoot to intentionally drop the mask.

workDir itself flows through filepath.Abs + filepath.EvalSymlinks so that WritePaths[0] is the canonical filesystem path (symlinked workDirs no longer leak their pre-resolve path into the writable set).

func MCPServerPolicy

func MCPServerPolicy(workDir, dataRoot string) Policy

MCPServerPolicy returns a policy for MCP stdio server processes. Read-global, write-/tmp only, network allowed (MCP servers need network). The first ancestor `.git` directory discovered via walk-up from workDir and the lango control-plane (dataRoot) are denied so that misbehaving MCP server child processes cannot read or write git metadata or lango's internal state. This mirrors DefaultToolPolicy's baseline deny — both sandboxed-children surfaces share the same protection.

Baseline deny paths are added only when they exist as directories. An empty workDir or a workDir with no ancestor `.git` directory is silently skipped. A missing or non-directory dataRoot is also skipped so that isolated unit tests and minimal environments can still build the policy. Pass empty strings to intentionally drop the masks.

func StrictToolPolicy

func StrictToolPolicy(workDir, dataRoot string) Policy

StrictToolPolicy returns a maximally restrictive policy. Currently identical to DefaultToolPolicy because .git denial and control-plane masking are now part of the baseline. The function is preserved as a separate symbol so callers (and future strict-only options) can branch later without another signature migration.

type ProcessPolicy

type ProcessPolicy struct {
	// AllowFork permits fork/exec within the sandbox.
	AllowFork bool

	// AllowSignals permits sending signals to other processes.
	AllowSignals bool
}

ProcessPolicy defines process creation controls.

type SandboxStatus

type SandboxStatus struct {
	// Enabled reflects the sandbox.enabled config value.
	Enabled bool
	// FailClosed reflects the sandbox.failClosed config value.
	FailClosed bool
	// Isolator is the active OS isolator (never nil).
	Isolator OSIsolator
	// Capabilities holds the platform primitive probe results.
	Capabilities PlatformCapabilities
}

SandboxStatus summarizes the operational state of the sandbox subsystem. It combines configuration values with runtime probe results.

func NewSandboxStatus

func NewSandboxStatus(enabled, failClosed bool, iso OSIsolator) SandboxStatus

NewSandboxStatus creates a SandboxStatus from config and an isolator. If iso is nil (sandbox disabled), a disabledIsolator is substituted.

type SeatbeltIsolator

type SeatbeltIsolator struct{}

SeatbeltIsolator is unavailable on non-macOS platforms.

func NewSeatbeltIsolator

func NewSeatbeltIsolator() *SeatbeltIsolator

NewSeatbeltIsolator returns a stub on non-macOS.

func (*SeatbeltIsolator) Apply

func (s *SeatbeltIsolator) Apply(_ context.Context, _ *exec.Cmd, _ Policy) error

func (*SeatbeltIsolator) Available

func (s *SeatbeltIsolator) Available() bool

func (*SeatbeltIsolator) Name

func (s *SeatbeltIsolator) Name() string

func (*SeatbeltIsolator) Reason

func (s *SeatbeltIsolator) Reason() string

Jump to

Keyboard shortcuts

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