Documentation
¶
Overview ¶
Package sandlock provides Go bindings for sandlock, a lightweight Linux process sandbox built on Landlock, seccomp-bpf, and seccomp user notification. It binds the sandlock C ABI (libsandlock_ffi) via cgo and mirrors the Python SDK's Sandbox surface.
The bindings are Linux-only. By default the runtime requires Linux 6.12+ (Landlock ABI v6); use the AllowDegraded or Disable fields to run on older kernels by degrading or disabling the v6-only protections. See the project README for the full kernel feature matrix.
Building ¶
cgo links against libsandlock_ffi, which is produced by the Rust workspace:
cargo build --release # writes target/release/libsandlock_ffi.so cd go && go test ./...
The default cgo link flags resolve the library relative to this package (../target/release). Build from a checkout of the sandlock repository, or adjust the link flags for an installed library.
Quick start ¶
sb := &sandlock.Sandbox{
FSReadable: []string{"/usr", "/lib", "/lib64", "/bin", "/etc"},
FSWritable: []string{"/tmp"},
}
res, err := sb.Run(context.Background(), "echo", "hello")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d: %s", res.ExitCode, res.Stdout)
Index ¶
- Variables
- func Confine(s *Sandbox) error
- func LandlockABIVersion() int
- func MinLandlockABI() int
- func ProtectionMinABI(p Protection) int
- func SyscallNr(name string) (int, error)
- type BranchAction
- type Change
- type ChangeKind
- type DryRunResult
- type ExitReason
- type PolicyContext
- func (c *PolicyContext) AllowPath(path string) error
- func (c *PolicyContext) DenyPath(path string) error
- func (c *PolicyContext) GrantNetwork(ips []string) error
- func (c *PolicyContext) RestrictMaxMemory(bytes uint64)
- func (c *PolicyContext) RestrictMaxProcesses(n uint32)
- func (c *PolicyContext) RestrictNetwork(ips []string) error
- func (c *PolicyContext) RestrictPIDNetwork(pid uint32, ips []string) error
- type PolicyDecision
- type PolicyFunc
- type Process
- type Protection
- type Result
- type Sandbox
- func (s *Sandbox) DryRun(ctx context.Context, cmd ...string) (*DryRunResult, error)
- func (s *Sandbox) Popen(stdio Stdio, cmd ...string) (*Process, error)
- func (s *Sandbox) Run(ctx context.Context, cmd ...string) (*Result, error)
- func (s *Sandbox) RunInteractive(ctx context.Context, cmd ...string) (int, error)
- func (s *Sandbox) Spawn(cmd ...string) (*Process, error)
- type Stdio
- type StdioMode
- type SyscallCategory
- type SyscallEvent
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidString = errors.New("sandlock: string contains NUL byte")
ErrInvalidString is returned when a string passed to the SDK contains an interior NUL byte, which cannot cross the C ABI boundary intact.
var ErrNotRunning = errors.New("sandlock: process is not running")
ErrNotRunning is returned by *Process lifecycle methods when no process is currently running in the handle.
Functions ¶
func Confine ¶
Confine applies the Sandbox's Landlock filesystem rules to the current process, in place and irreversibly. Only filesystem fields are honored; configuration that requires a supervisor or a fresh child (seccomp, network, resource limits, environment, etc.) is rejected by the core rather than silently ignored.
func LandlockABIVersion ¶
func LandlockABIVersion() int
LandlockABIVersion returns the Landlock ABI version supported by the running kernel, or -1 if Landlock is unavailable.
func MinLandlockABI ¶
func MinLandlockABI() int
MinLandlockABI returns the minimum Landlock ABI version this build requires.
func ProtectionMinABI ¶ added in v0.8.4
func ProtectionMinABI(p Protection) int
ProtectionMinABI returns the minimum Landlock ABI version the host kernel must support for the given protection to be available, or 0 for an unknown protection value.
Types ¶
type BranchAction ¶
type BranchAction uint8
BranchAction is the action taken on a copy-on-write working-directory branch when the sandbox exits. The zero value, BranchActionDefault, leaves the choice to sandlock's own defaults (commit on success, abort on error).
const ( // BranchActionDefault defers to sandlock's built-in default. BranchActionDefault BranchAction = iota // BranchActionCommit merges the branch's writes into the parent on exit. BranchActionCommit // BranchActionAbort discards all of the branch's writes on exit. BranchActionAbort // BranchActionKeep leaves the branch in place for the caller to handle. BranchActionKeep )
type Change ¶
type Change struct {
Kind ChangeKind // 'A' added, 'M' modified, 'D' deleted
Path string // path relative to the working directory
}
Change is a single filesystem change detected by DryRun.
type ChangeKind ¶
type ChangeKind byte
ChangeKind classifies a filesystem change observed during a dry run.
const ( ChangeAdded ChangeKind = 'A' ChangeModified ChangeKind = 'M' ChangeDeleted ChangeKind = 'D' )
type DryRunResult ¶
DryRunResult is the outcome of a dry run: a normal Result plus the list of filesystem changes the command would have made, all of which are discarded.
type ExitReason ¶ added in v0.8.5
type ExitReason uint32
ExitReason is why a sandboxed process terminated. It mirrors the C sandlock_exit_reason enum. Linux bottoms both a timeout and an OOM kill out in SIGKILL, so there is no distinct OOM reason: a timeout sandlock enforced is ReasonTimeout, any other kill is ReasonKilled.
const ( // ReasonExited: exited normally with a code (Result.ExitCode). ReasonExited ExitReason = 0 // ReasonSignaled: terminated by a signal (Result.Signal). ReasonSignaled ExitReason = 1 // ReasonKilled: killed with no recoverable signal number. ReasonKilled ExitReason = 2 // ReasonTimeout: killed by sandlock because it exceeded its timeout. ReasonTimeout ExitReason = 3 )
type PolicyContext ¶
type PolicyContext struct {
// contains filtered or unexported fields
}
PolicyContext lets a PolicyFunc adjust selected live policy state.
A PolicyContext is valid only during the PolicyFunc call that received it. Do not retain it after the callback returns.
func (*PolicyContext) AllowPath ¶
func (c *PolicyContext) AllowPath(path string) error
AllowPath removes a dynamic path denial previously added by DenyPath.
func (*PolicyContext) DenyPath ¶
func (c *PolicyContext) DenyPath(path string) error
DenyPath dynamically denies access to path for mediated openat checks.
func (*PolicyContext) GrantNetwork ¶
func (c *PolicyContext) GrantNetwork(ips []string) error
GrantNetwork grants ips within the sandbox's immutable network ceiling.
func (*PolicyContext) RestrictMaxMemory ¶
func (c *PolicyContext) RestrictMaxMemory(bytes uint64)
RestrictMaxMemory permanently restricts the live max-memory policy.
func (*PolicyContext) RestrictMaxProcesses ¶
func (c *PolicyContext) RestrictMaxProcesses(n uint32)
RestrictMaxProcesses permanently restricts the live max-processes policy.
func (*PolicyContext) RestrictNetwork ¶
func (c *PolicyContext) RestrictNetwork(ips []string) error
RestrictNetwork permanently restricts the live network policy to ips.
func (*PolicyContext) RestrictPIDNetwork ¶
func (c *PolicyContext) RestrictPIDNetwork(pid uint32, ips []string) error
RestrictPIDNetwork restricts network access for a specific process.
type PolicyDecision ¶
type PolicyDecision int32
PolicyDecision is the result returned by a PolicyFunc.
const ( // DecisionAllow allows the syscall. DecisionAllow PolicyDecision = 0 // DecisionDeny denies the syscall with EPERM. DecisionDeny PolicyDecision = -1 // DecisionAudit allows the syscall and flags it for audit. DecisionAudit PolicyDecision = -2 )
func Audit ¶
func Audit() PolicyDecision
Audit returns a decision that allows the syscall and flags it for audit.
func DenyWith ¶
func DenyWith(errnoValue int) PolicyDecision
DenyWith returns a decision that denies the syscall with errnoValue.
type PolicyFunc ¶
type PolicyFunc func(event SyscallEvent, ctx *PolicyContext) PolicyDecision
PolicyFunc is a dynamic policy callback invoked from sandlock's policy-fn worker thread. Callbacks may be invoked concurrently with other sandbox activity, so captured state should be synchronized when mutated.
type Process ¶
type Process struct {
// Caller-owned stdio for a process started by Popen. Each is non-nil only
// for a stream wired StdioPiped; it owns the pipe fd (closing the file
// closes the fd). Nil for Spawn'd processes and for inherit/null streams.
Stdin *os.File
Stdout *os.File
Stderr *os.File
// contains filtered or unexported fields
}
Process is a live sandboxed process started by Spawn. It supports PID inspection, pause/resume/kill via the process group, and Wait. A Process holds at most one running command; create separate Spawns for concurrency.
The underlying FFI handle is not safe for concurrent access, so all handle operations are serialized. Pause/Resume/Kill act on the OS process group by PID and touch no handle state, so they remain usable while Wait blocks on the handle — that is how Kill interrupts a blocked Wait. Ports, by contrast, reads the handle and is reported as empty while a Wait is in flight.
func (*Process) Close ¶
Close releases the process handle, killing the process if it is still running. It is safe to call multiple times.
func (*Process) Kill ¶
Kill sends SIGKILL to the sandbox process group. It is idempotent: a process that already exited (ESRCH) or was already reaped by Wait (ErrNotRunning) is not an error — killing it is a no-op success, matching the FFI (sandlock_handle_kill returns 0) and the Python binding (silent no-op after wait()). This keeps the "Kill from another goroutine" escape hatch race-free: a Kill that fires just as Wait reaps the child still returns nil.
func (*Process) Ports ¶
Ports returns the current virtual-to-real TCP port mappings while the process is running. It is non-empty only when PortRemap is enabled and at least one port has been remapped.
func (*Process) Wait ¶
Wait blocks until the process exits, returns its Result, and releases the handle. After Wait the Process is no longer running.
The Result carries the exit status only. Unlike Run, a Popen'd process sends any StdioPiped output to the caller through the Stdout/Stderr fields (and StdioInherit/StdioNull output to the inherited fd or /dev/null), so it is never captured into Result.Stdout/Result.Stderr — read piped bytes off p.Stdout/p.Stderr, not off the Result.
The blocking native wait runs without holding the mutex so that Kill (and Pause/Resume), which signal the process group by PID, can run concurrently and interrupt it. The waiting flag reserves exclusive use of the handle for the duration, so no other handle operation aliases it.
Wait closes a still-open piped Stdin to deliver EOF, so do not write to Stdin from another goroutine while Wait runs — the write would race a closing fd.
type Protection ¶ added in v0.8.4
type Protection uint32
Protection identifies a single Landlock protection whose enforcement posture can be opted out of via the Sandbox AllowDegraded / Disable fields. The values match the sandlock C ABI discriminants.
const ( ProtectionFSRefer Protection = 0 // file reparenting (Landlock ABI v2) ProtectionFSTruncate Protection = 1 // truncate(2) (ABI v3) ProtectionNetTCP Protection = 2 // TCP bind/connect (ABI v4) ProtectionFSIoctlDev Protection = 3 // device ioctl(2) (ABI v5) ProtectionSignalScope Protection = 4 // signal scoping (ABI v6) ProtectionAbstractUnixSocketScope Protection = 5 // abstract UNIX socket scoping (ABI v6) )
type Result ¶
type Result struct {
ExitCode int // process exit code, or -1 if terminated abnormally
Reason ExitReason // why the process terminated (exit / signal / kill / timeout)
Signal int // signal number for a ReasonSignaled result, else -1
Success bool // true when the process exited 0
Stdout []byte // captured standard output
Stderr []byte // captured standard error
}
Result is the outcome of a captured run.
type Sandbox ¶
type Sandbox struct {
// Filesystem (Landlock).
FSReadable []string // paths the sandbox may read (and execute)
FSWritable []string // paths the sandbox may write
FSDenied []string // paths explicitly denied
Workdir string // copy-on-write root; enables COW protection of this tree
Cwd string // child working directory (chdir target)
Chroot string // path to chroot into before applying confinement
// FSMount maps virtual paths inside the chroot to host directories,
// like a bind mount without kernel mounts or root.
FSMount map[string]string
// Protection opt-out (Landlock per-protection posture).
//
// By default every protection is enforced strictly, which requires the
// host kernel to support it (the highest floor is ABI v6); on an older
// kernel a strict protection it cannot satisfy makes the build fail.
// AllowDegraded marks protections to enforce where the host supports them
// and silently skip otherwise; Disable turns them off entirely. Together
// they let a sandbox run on a kernel below the default v6 floor.
//
// A protection listed in both fields is disabled: Disable is applied
// last and takes precedence.
AllowDegraded []Protection
Disable []Protection
// Network.
//
// NetAllow entries are outbound endpoint rules. The bare form is TCP
// ("api.openai.com:443", "github.com:22,443", ":53"); a target may be a
// host, IP, or CIDR ("10.0.0.0/8:443", "[2606:4700::/32]:443"), and
// scheme prefixes opt other protocols in ("tcp://", "udp://host:port",
// "udp://*", "icmp://host", "icmp://*"). Empty denies all outbound.
NetAllow []string
// NetDeny is the inverse of NetAllow: default-allow networking, block
// these targets. Same grammar as NetAllow except targets must be a
// literal IP/CIDR or "*" (no hostnames; use HTTPDeny for domains).
// Mutually exclusive with NetAllow.
NetDeny []string
// NetAllowBind lists TCP ports the sandbox may bind/listen on
// (default-deny). Each entry is a comma-separated list of single ports
// or inclusive "lo-hi" ranges ("8080", "3000-3010", "8080,9000-9005").
// The "*" wildcard allows binding any port and cannot be mixed with
// port entries.
// Mutually exclusive with NetDenyBind.
NetAllowBind []string
// NetDenyBind is the inverse of NetAllowBind: default-allow binding,
// deny these TCP ports (same port syntax). Mutually exclusive with
// NetAllowBind.
NetDenyBind []string
PortRemap bool // transparent per-sandbox TCP port virtualization
// HTTP ACL (method + host + path rules via a transparent proxy).
HTTPAllow []string // allow rules, "METHOD host/path"
HTTPDeny []string // deny rules, checked before allow rules
HTTPPorts []int // ports to intercept (defaults to 80, plus 443 with a CA)
HTTPCAFile string // PEM CA certificate for HTTPS MITM
HTTPKeyFile string // PEM CA private key (required with HTTPCAFile)
// Resource limits.
MaxMemory string // e.g. "512M"; empty = unlimited
MaxDisk string // disk quota for COW storage, e.g. "1G"
MaxProcesses uint32 // peak concurrent process cap; 0 = sandlock default
MaxCPU uint8 // CPU throttle, percent of one core (1-100); 0 = unset
MaxOpenFiles uint32 // RLIMIT_NOFILE soft+hard in the child, clamped to sandlock's own limits; 0 = inherit
CPUCores []uint32 // cores to pin to via sched_setaffinity
NumCPUs uint32 // synthetic /proc/cpuinfo processor count; 0 = unset
GPUDevices []uint32 // GPU device indices to expose; nil = none
// Syscall filtering (on top of sandlock's default blocklist).
ExtraAllowSyscalls []string // syscall groups to allow, e.g. "sysv_ipc"
ExtraDenySyscalls []string // extra syscall names to block
// Determinism.
RandomSeed *uint64 // seed getrandom() deterministically
TimeStart string // virtual clock start: RFC3339 or unix seconds
NoRandomizeMemory bool // disable ASLR
NoHugePages bool // disable transparent huge pages
DeterministicDirs bool // sort readdir() entries
// Environment.
CleanEnv bool // start from a minimal environment
Env map[string]string // variables to set/override in the child
// Misc.
UID *int // map to this UID inside a user namespace; nil = unset
GID *int // map to this GID inside the user namespace; must be set together with UID
NoCoredump bool // disable core dumps and restrict /proc/pid access
// Copy-on-write branch handling.
FSStorage string // storage directory for COW deltas
OnExit BranchAction // branch action on normal exit
OnError BranchAction // branch action on error exit
// Name is the sandbox name and its virtual hostname inside the sandbox.
// Empty auto-generates "sandbox-{pid}".
Name string
// PolicyFn receives dynamic syscall events and may return an allow/deny
// decision or modify live policy through the supplied context.
PolicyFn PolicyFunc
}
Sandbox holds the policy configuration for confining a process. Every field is optional; an unset field means "no restriction" unless documented otherwise. sandlock's default syscall blocklist is always applied.
A Sandbox value carries no runtime state: Run, RunInteractive, and DryRun build a fresh native policy on each call, so a single Sandbox may be reused and shared across goroutines. Use Spawn for explicit process lifecycle control, which returns an independent *Process handle.
func (*Sandbox) DryRun ¶
DryRun executes cmd against a temporary copy-on-write layer, collects the filesystem changes it would have made, then discards them. It requires Workdir to be set.
func (*Sandbox) Popen ¶ added in v0.8.5
Popen forks the sandboxed child with per-stream stdio and returns a live Process without waiting — the streaming counterpart of Spawn. For each stream set to StdioPiped, the matching Process field (Stdin/Stdout/Stderr) is an *os.File the caller reads/writes while the child runs; inherit/null streams leave it nil. The zero Stdio inherits all three (identical to Spawn).
Deadlock warning (as with os/exec pipes): a piped Stdin is yours — close it before Wait, or a child that reads to EOF (e.g. cat) never exits and Wait blocks. Likewise drain a piped Stdout/Stderr before Wait, or a child that fills the pipe buffer blocks on write. As an escape hatch, Kill from another goroutine interrupts a blocked Wait; Close reaps the child and closes the streams. Wait closes a still-open piped Stdin for you to deliver EOF.
func (*Sandbox) Run ¶
Run executes cmd in the sandbox, capturing stdout and stderr, and waits for it to finish. If ctx carries a deadline, the process is killed and a result with ExitCode -1 is returned once it elapses. ctx cancellation without a deadline does not preempt an already-running child.
func (*Sandbox) RunInteractive ¶
RunInteractive executes cmd with the calling process's stdio inherited (no capture) and returns the exit code. The context is honored only as a pre-run cancellation check; interactive runs are not interrupted by a deadline.
type Stdio ¶ added in v0.8.5
Stdio selects the wiring of a Popen'd process's three standard streams. The zero value wires all three as StdioInherit, matching Spawn.
type StdioMode ¶ added in v0.8.5
type StdioMode uint32
StdioMode selects how one of a Popen'd process's standard streams is wired. The values are the stable ABI discriminants shared with the C/Rust core.
const ( // StdioInherit shares the parent's fd (the child writes to the same // terminal/file). It is the zero value, so an unset stream inherits. StdioInherit StdioMode = 0 // StdioPiped connects the stream to a pipe; Popen hands the caller the // owning end as an *os.File on the returned Process. StdioPiped StdioMode = 1 // StdioNull connects the stream to /dev/null. StdioNull StdioMode = 2 )
type SyscallCategory ¶
type SyscallCategory uint8
SyscallCategory is the high-level category of an intercepted syscall event.
const ( // CategoryFile covers filesystem operations such as openat and unlinkat. CategoryFile SyscallCategory = iota // CategoryNetwork covers network operations such as connect and bind. CategoryNetwork // CategoryProcess covers process lifecycle operations such as execve. CategoryProcess // CategoryMemory covers memory-management operations such as mmap. CategoryMemory )
func (SyscallCategory) String ¶
func (c SyscallCategory) String() string
String returns the category name used by the Python SDK.
type SyscallEvent ¶
type SyscallEvent struct {
Syscall string
Category SyscallCategory
PID uint32
ParentPID uint32
Host string
Port uint16
Denied bool
Argv []string
}
SyscallEvent is a policy_fn event delivered by the sandbox supervisor.
Path strings are intentionally absent. Path-based access control belongs in Landlock rules (FSReadable, FSWritable, FSDenied). Argv is populated only for execve/execveat events, where sandlock freezes sibling tasks before exposing it to the policy callback.
func (SyscallEvent) ArgvContains ¶
func (e SyscallEvent) ArgvContains(sub string) bool
ArgvContains reports whether any argv element contains sub.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
Command basic demonstrates running a command under a sandlock sandbox with a read-only root filesystem and a single writable directory.
|
Command basic demonstrates running a command under a sandlock sandbox with a read-only root filesystem and a single writable directory. |
|
internal
|
|
|
policy
Package policy holds pure, platform-independent parsing helpers shared by the sandlock Go SDK.
|
Package policy holds pure, platform-independent parsing helpers shared by the sandlock Go SDK. |