Help us shape the future of Go! Take the 2026 Go Developer Survey

arapuca

package module
v0.2.4 Latest Latest
Warning

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

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

README

go-arapuca

Go bindings for arapuca, a Linux/macOS process sandbox. Wraps the C API via cgo to provide idiomatic Go types for launching sandboxed subprocesses.

What arapuca enforces

On Linux: Landlock filesystem restrictions, seccomp BPF syscall filtering, cgroups v2 resource limits (memory, CPU, PIDs), network namespace isolation, rlimits, pdeathsig, setsid, environment sanitization.

On macOS: Apple's sandbox-exec (Seatbelt) with deny-default profiles, rlimits, memory polling, parent-PID watchdog.

Prerequisites

  • Go 1.25+ with CGO_ENABLED=1
  • C compiler (gcc or clang)
  • pkg-config
  • libarapuca.a, arapuca.h, and arapuca.pc installed where pkg-config can find them (see Building the C library)

Install

go get github.com/sergio-correia/go-arapuca

Versioning

go-arapuca links against arapuca's C ABI via pkg-config. There is no automatic version coupling — you must keep them in sync. Each go-arapuca release documents the minimum arapuca version it requires:

go-arapuca arapuca (min)
v0.2.0+ v0.1.1
v0.1.x v0.1.0

If you see link errors or crashes, rebuild and reinstall the C library first.

Usage

Process-level sandbox
package main

import (
    "context"
    "fmt"
    "log"

    arapuca "github.com/sergio-correia/go-arapuca"
)

func main() {
    sb, err := arapuca.New()
    if err != nil {
        log.Fatal(err)
    }
    defer sb.Close()

    cfg := arapuca.Config{
        Profile: arapuca.Profile{
            ReadPaths:   []string{"/usr", "/lib", "/lib64", "/bin", "/etc", "/dev"},
            WritePaths:  []string{"/tmp/workspace"},
            MaxMemoryMB: 2048,
            MaxPIDs:     256,
            MaxCPUPct:   200, // 2 cores
            UseNetNS:    true,
        },
        TaskID:  "task-42",
        Phase:   "execute",
        WorkDir: "/tmp/workspace",
    }

    proc, err := sb.Launch(context.Background(), cfg, "/usr/bin/python3", []string{"agent.py"}, nil)
    if err != nil {
        log.Fatal(err)
    }

    exitCode, err := proc.Wait()
    if err != nil {
        log.Printf("process error: %v", err)
    }

    stats := proc.ResourceStats()
    fmt.Printf("exit=%d peak_memory=%d bytes\n", exitCode, stats.MemoryPeakBytes)

    proc.Cleanup()
}
Micro-VM isolation (Linux only, requires -tags microvm)
package main

import (
    "context"
    "fmt"
    "log"

    arapuca "github.com/sergio-correia/go-arapuca"
)

func main() {
    if !arapuca.MicroVmAvailable() {
        log.Fatal("micro-VM isolation not available (no KVM?)")
    }

    // Pull image (~500MB first time, cached for reuse).
    imagePath, err := arapuca.ImagePull("fedora", "42")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("image:", imagePath)

    sb, err := arapuca.New()
    if err != nil {
        log.Fatal(err)
    }
    defer sb.Close()

    cfg := arapuca.Config{
        Profile: arapuca.Profile{
            ReadPaths:  []string{"/home/user/project"},
            WritePaths: []string{"/tmp/workspace"},
            Isolation: &arapuca.MicroVmIsolation{
                Distro:  "fedora",
                Version: "42",
                CPUs:    2,
                MemMB:   2048,
            },
        },
        TaskID: "vm-task-1",
        Phase:  "execute",
    }

    proc, err := sb.Launch(context.Background(), cfg, "make", []string{"test"}, nil)
    if err != nil {
        log.Fatal(err)
    }

    exitCode, _ := proc.Wait()
    fmt.Printf("exit=%d\n", exitCode)
    proc.Cleanup()
}

API

Sandbox
arapuca.New() (*Sandbox, error)           // create sandbox handle
sb.Launch(ctx, cfg, cmd, args, extraFiles)  // launch sandboxed subprocess
sb.CgroupsAvailable() bool                 // probe cgroups v2
sb.Close()                                 // release handle
Process
proc.PID() int                     // subprocess PID
proc.Wait() (int, error)           // wait for exit (code, error)
proc.ResourceStats() ResourceUsage // cgroup stats (before Cleanup)
proc.OOMCount() int                // OOM kill count (before Cleanup)
proc.Cleanup()                     // release resources
Micro-VM (requires -tags microvm)
arapuca.MicroVmAvailable() bool                  // probe KVM + qemu-img
arapuca.ImagePull(distro, version) (string, error) // download and cache image

Without the build tag, MicroVmAvailable() returns false and ImagePull() returns an error.

Utilities
arapuca.MakeSocketDir() (string, error)  // temp dir for Unix sockets
arapuca.MakeTmpDir(taskID) (string, error)
arapuca.WrapperPath() string             // find arapuca binary
arapuca.LandlockABIVersion() int         // 0 if unavailable
arapuca.NetNSAvailable() bool
arapuca.DiskUsageMB(path) uint64
Types
type Profile struct {
    ReadPaths, WritePaths []string
    MaxMemoryMB           uint64             // 0 = no limit
    MaxCPUPct             uint32             // 0 = no limit; 200 = 2 cores
    MaxPIDs               uint32
    MaxFileSizeMB         uint64
    UseNetNS              bool
    Isolation             *MicroVmIsolation  // nil = process-level sandbox
}

type MicroVmIsolation struct {
    Distro    string  // e.g. "fedora", "centos" (or use ImagePath)
    Version   string  // e.g. "42", "9"
    ImagePath string  // explicit qcow2 path (alternative to Distro/Version)
    CPUs      uint32
    MemMB     uint32
}

type Config struct {
    Profile            Profile
    SocketDir          string
    TaskID             string
    Phase              string
    WorkDir            string
    Stdin              *os.File          // nil = inherit
    Stdout, Stderr     *os.File          // nil = inherit
    NetworkProxySocket string
    Env                map[string]string // extra env vars for subprocess
}

type ResourceUsage struct {
    MemoryCurrentBytes, MemoryPeakBytes int64
    CPUUsageSeconds                     float64
    PIDCount                            int64
    IOReadBytes, IOWriteBytes           int64
}

Thread Safety

  • Sandbox is safe for concurrent use (mutex-protected).
  • Process methods are safe for concurrent use (mutex-protected).
  • Error checking uses runtime.LockOSThread() to pin goroutines to OS threads (arapuca uses thread-local error storage).
  • Context cancellation sends SIGKILL to the process group.

Building the C library

go-arapuca links against libarapuca.a (a Rust static library) discovered via pkg-config. Two build modes are available:

Core sandbox (all platforms)
make setup

Installs process-level sandbox support only. No libkrun or OpenSSL development packages required.

With micro-VM support (Linux only)
make setup-microvm

Installs with micro-VM isolation via libkrun. Requires libkrun-devel and openssl-devel (or equivalent) packages. Build Go code with -tags microvm:

go build -tags microvm ./...
Build matrix
arapuca install go-arapuca build Result
make setup go build core sandbox only
make setup go build -tags microvm link error
make setup-microvm go build core sandbox only
make setup-microvm go build -tags microvm full micro-VM support
Custom paths
# Custom arapuca checkout and install prefix
make setup ARAPUCA_DIR=/path/to/arapuca PREFIX=/opt/arapuca

# Ensure pkg-config finds the installed library
export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig

Re-run make setup (or make setup-microvm) whenever the arapuca library changes.

License

Apache-2.0

Documentation

Overview

Package arapuca provides Go bindings for the arapuca process sandbox library via cgo. It wraps the C API to provide idiomatic Go types for sandboxed subprocess management.

Arapuca enforces OS-level isolation (Landlock, seccomp BPF, cgroups v2, network namespaces on Linux; sandbox-exec on macOS) on untrusted subprocesses.

Thread safety: arapuca_last_error() uses thread-local storage. All cgo call sequences that check errors use runtime.LockOSThread() to prevent Go's goroutine scheduler from moving the goroutine to a different OS thread between the call and the error check.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DiskUsageMB

func DiskUsageMB(path string) uint64

DiskUsageMB returns the disk usage of a directory in MB.

func EnableSelfExecMode added in v0.2.1

func EnableSelfExecMode()

EnableSelfExecMode configures the library to use the current executable as the sandbox wrapper binary, eliminating the need for a separate arapuca binary. Must be called before any call to Launch; if called after, only subsequent launches are affected.

func ImagePull added in v0.2.0

func ImagePull(_, _ string) (string, error)

ImagePull is unavailable without the microvm build tag.

func LandlockABIVersion

func LandlockABIVersion() int

LandlockABIVersion probes the kernel's Landlock ABI version. Returns 0 if Landlock is unavailable.

func MakeSocketDir

func MakeSocketDir() (string, error)

MakeSocketDir creates a temporary directory with mode 0700 and a random suffix for per-agent Unix sockets. The caller is responsible for removing the directory when done.

func MakeTmpDir

func MakeTmpDir(taskID string) (string, error)

MakeTmpDir creates a temporary directory for the given task with a random suffix. The caller is responsible for removing it.

func MicroVmAvailable added in v0.2.0

func MicroVmAvailable() bool

MicroVmAvailable always returns false without the microvm build tag.

func NetNSAvailable

func NetNSAvailable() bool

NetNSAvailable probes whether network namespace isolation works.

func WrapperPath

func WrapperPath() string

WrapperPath returns the path to the arapuca binary, or empty string if not found.

Types

type AllowedHost added in v0.2.3

type AllowedHost struct {
	Host string // Exact hostname or ".suffix" for wildcard matching.
	Port uint16 // TCP port (1-65535).
}

AllowedHost specifies an outbound host:port that the CONNECT proxy will allow connections to from inside the network namespace sandbox. Use a leading dot in Host for suffix matching (e.g. ".googleapis.com" matches any subdomain). Port must be 1-65535.

When AllowedHosts is non-empty on Config, the library automatically enables UseNetNS and forks a CONNECT proxy that permits only the listed destinations.

type Config

type Config struct {
	Profile            Profile
	SocketDir          string            // Per-agent socket directory.
	TaskID             string            // Task identifier.
	Phase              string            // Current phase (opaque to arapuca).
	WorkDir            string            // Working directory (empty = inherit).
	Stdin              *os.File          // Redirect stdin (nil = inherit).
	Stdout             *os.File          // Redirect stdout (nil = inherit).
	Stderr             *os.File          // Redirect stderr (nil = inherit).
	NetworkProxySocket string            // Path to network proxy Unix socket.
	AllowedHosts       []AllowedHost     // Outbound allowlist for CONNECT proxy.
	Env                map[string]string // Caller-supplied env vars for subprocess.
}

Config holds the full configuration for launching a sandboxed process.

type MicroVmIsolation added in v0.2.0

type MicroVmIsolation struct {
	Distro    string // Distro name (e.g. "fedora", "centos"). Mutually exclusive with ImagePath.
	Version   string // Distro version (e.g. "42", "9"). Mutually exclusive with ImagePath.
	ImagePath string // Explicit qcow2 path. Mutually exclusive with Distro/Version.
	CPUs      uint32 // Number of vCPUs (must be > 0).
	MemMB     uint32 // Memory in MB (must be > 0).
}

MicroVmIsolation configures micro-VM isolation via libkrun. Set either Distro+Version or ImagePath, not both. Requires building with -tags microvm against a microvm-enabled libarapuca.a (make install INSTALL_FEATURES=microvm).

type Process

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

Process represents a running sandboxed subprocess.

func (*Process) Cleanup

func (p *Process) Cleanup()

Cleanup releases the subprocess's resources (temp directory, cgroup). Must only be called after Wait() returns. Safe to call multiple times.

func (*Process) OOMCount

func (p *Process) OOMCount() int

OOMCount reads the OOM kill count from the subprocess's cgroup. Must be called before Cleanup().

func (*Process) PID

func (p *Process) PID() int

PID returns the process ID of the sandboxed subprocess.

func (*Process) ResourceStats

func (p *Process) ResourceStats() ResourceUsage

ResourceStats reads resource usage from the subprocess's cgroup. Must be called before Cleanup(). Returns zero values if cgroups are unavailable.

func (*Process) Wait

func (p *Process) Wait() (int, error)

Wait waits for the subprocess to exit. Returns the exit code and any error. If the process was killed by a signal, the exit code is 0 and the error describes the signal.

type Profile

type Profile struct {
	ReadPaths        []string           // Allowed read-only paths.
	WritePaths       []string           // Allowed read-write paths.
	MaxMemoryMB      uint64             // Memory limit in MB (0 = no limit).
	MaxCPUPct        uint32             // CPU percentage (0 = no limit; 200 = 2 cores).
	MaxPIDs          uint32             // Max processes (0 = no limit).
	MaxFileSizeMB    uint64             // Max file size in MB (0 = no limit).
	MaxOpenFiles     uint64             // Max open file descriptors (0 = no limit, RLIMIT_NOFILE).
	AllowExec        bool               // Allow execve on binaries in read AND write paths. Required for shebang scripts (e.g. Python handlers). Caution: with WritePaths, this creates a write-then-exec surface.
	CgroupBestEffort bool               // When true, launch without cgroup limits if controllers aren't delegated (applies rlimit fallbacks). When false (default), missing controllers cause a hard failure.
	UseNetNS         bool               // Use network namespace isolation.
	UsePidNS         bool               // Use PID namespace isolation (process appears as PID 1).
	DnsCapture       bool               // Capture DNS queries as audit events (requires UseNetNS).
	SeccompProfile   SeccompProfileKind // Seccomp profile: SeccompProfileDefault (strict), SeccompProfileStrict, or SeccompProfileBaseline (for Bun/Claude Code).
	Isolation        *MicroVmIsolation  // Micro-VM isolation (nil = process-level sandbox).
}

Profile defines the restrictions applied to a sandboxed subprocess.

type ResourceUsage

type ResourceUsage struct {
	MemoryCurrentBytes int64
	MemoryPeakBytes    int64
	CPUUsageSeconds    float64
	PIDCount           int64
	IOReadBytes        int64
	IOWriteBytes       int64
}

ResourceUsage holds cgroup v2 resource usage statistics.

type Sandbox

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

Sandbox wraps the arapuca sandbox handle. It is the entry point for launching sandboxed subprocesses. Create with New().

func New

func New() (*Sandbox, error)

New creates a new sandbox for the current platform.

func (*Sandbox) CgroupsAvailable

func (s *Sandbox) CgroupsAvailable() bool

CgroupsAvailable reports whether cgroups v2 resource limits are available on this system.

func (*Sandbox) Close

func (s *Sandbox) Close()

Close releases the sandbox handle. Safe to call multiple times.

func (*Sandbox) Launch

func (s *Sandbox) Launch(ctx context.Context, cfg Config, cmd string, args []string, extraFiles []*os.File) (*Process, error)

Launch starts a sandboxed subprocess. The subprocess is isolated according to the Config's Profile. Extra file descriptors in extraFiles are inherited by the subprocess at FD positions 3, 4, ...

The returned Process must be waited on and cleaned up.

Context cancellation: if ctx is cancelled, the subprocess is killed via SIGKILL to the process group.

type SeccompProfileKind added in v0.2.2

type SeccompProfileKind string

SeccompProfileKind is the seccomp enforcement level for the sandbox.

const (
	// SeccompProfileDefault uses the library default (currently Strict).
	SeccompProfileDefault SeccompProfileKind = ""
	// SeccompProfileStrict blocks AF_INET/AF_INET6, memfd_create, io_uring,
	// and other advanced syscalls. Suitable for untrusted code.
	SeccompProfileStrict SeccompProfileKind = "strict"
	// SeccompProfileBaseline blocks only sandbox-escape syscalls (ptrace,
	// mount, namespace ops, kernel modules, bpf). Suitable for
	// trusted-but-isolated applications such as Claude Code (Bun runtime)
	// that need full POSIX syscall access. Relies on Landlock + netns for
	// actual confinement.
	SeccompProfileBaseline SeccompProfileKind = "baseline"
)

Jump to

Keyboard shortcuts

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