Go Sandbox for AI Agents
Sandbox Go programs and AI agents at the kernel level. Use the nono CLI with the go_runtime security group, or embed nono-go directly into your Go application for programmatic control.
Why Go agents need kernel-level sandboxing
Go compiles to native binaries with full access to system calls. An AI agent written in Go can read any file, open any network connection, and spawn any subprocess the user can. Application-level restrictions are trivially bypassed by calling syscall directly.
nono enforces isolation at the kernel level using Landlock (Linux) and Seatbelt (macOS). The sandbox is irrevocable — once applied, it cannot be loosened, even by the sandboxed process itself. This applies to the process and all its children.
CLI Sandboxing
Use nono run --profile go-dev to sandbox any Go program. The go_runtime security group covers GOPATH, GOMODCACHE, and the Go toolchain paths. Everything else is denied by default.
Embedded SDK
The nono-go library provides CGo bindings to nono's Rust core. Self-sandbox your process with nono.Apply(caps). Static libraries for macOS (arm64, amd64) and Linux (amd64, arm64) are bundled. Requires Go 1.24+ and a C toolchain.
CLI sandboxing
The fastest way to sandbox a Go agent. The go-dev profile includes the go_runtime security group, which allows access to GOPATH, module cache, and the Go toolchain. Combine with network profiles and credential injection for production use.
# Sandbox a Go AI agent with read-write to the projectnono run --allow-cwd -- go run .# Use the go-dev profile (includes go_runtime security group)nono run --profile go-dev -- go run .# Restrict network to LLM API endpoints onlynono run --allow-cwd --network-profile minimal -- ./my-agent# Inject credentials from keychain (real keys never enter the sandbox)nono run --allow-cwd --credential openai -- ./my-agent# Sandbox go build and go testnono run --profile go-dev --allow-cwd -- go build ./...nono run --profile go-dev --allow-cwd -- go test ./...
{"meta": {"name": "go-agent","version": "1.0.0","description": "Go AI agent with controlled access"},"security": {"groups": ["go_runtime"]},"filesystem": {"allow": ["$HOME/.cache/go-build"],"read_file": ["$HOME/.gitconfig"]},"network": {"network_profile": "minimal"},"workdir": { "access": "readwrite" },"interactive": false}
Embedded SDK: nono-go
For programmatic control, embed nono-go directly into your Go application. Create a capability set with nono.New(), define allowed paths with AllowPath() and AllowFile() (using nono.AccessRead or nono.AccessReadWrite), control network with SetNetworkMode(), then call nono.Apply(caps) to lock it down irreversibly.
package mainimport ("errors""fmt""log""github.com/always-further/nono-go")func main() {// 1. Build a capability setcaps := nono.New()defer caps.Close()if err := caps.AllowPath("/home/user/data", nono.AccessRead); err != nil {log.Fatal(err)}if err := caps.AllowPath("/tmp", nono.AccessReadWrite); err != nil {log.Fatal(err)}if err := caps.SetNetworkMode(nono.NetworkBlocked); err != nil {log.Fatal(err)}// 2. Query permissions before applying (dry-run)qc, err := nono.NewQueryContext(caps)if err != nil {log.Fatal(err)}defer qc.Close()result, err := qc.QueryPath("/home/user/data/file.txt", nono.AccessRead)if err != nil {log.Fatal(err)}fmt.Println(result.Status) // nono.QueryAllowed// 3. Apply sandbox (irreversible)if err := nono.Apply(caps); err != nil {log.Fatal(err)}// 4. Error handling with sentinel accessorserr = caps.AllowPath("/nonexistent", nono.AccessRead)if errors.Is(err, nono.ErrPathNotFound()) {fmt.Println("path does not exist")}}
Serialize and restore state
SandboxState provides a JSON-serialisable snapshot of a capability set. Use StateFromCaps() to export and StateFromJSON() to restore. This is useful for persisting sandbox configurations or transferring them between processes.
package mainimport ("fmt""log""github.com/always-further/nono-go")func main() {caps := nono.New()if err := caps.AllowPath("/data", nono.AccessReadWrite); err != nil {log.Fatal(err)}if err := caps.SetNetworkMode(nono.NetworkBlocked); err != nil {log.Fatal(err)}// Serialize to JSONstate, err := nono.StateFromCaps(caps)if err != nil {log.Fatal(err)}defer state.Close()jsonStr, err := state.ToJSON()if err != nil {log.Fatal(err)}fmt.Println(jsonStr)// Restore from JSONrestored, err := nono.StateFromJSON(jsonStr)if err != nil {log.Fatal(err)}defer restored.Close()caps2, err := restored.ToCaps()if err != nil {log.Fatal(err)}defer caps2.Close()}
Error handling
All failing operations return *nono.Error. Use errors.Is with sentinel accessors like nono.ErrPathNotFound(), nono.ErrSandboxInit(), nono.ErrUnsupportedPlatform(), and others to test for specific failure kinds. Note the () — each sentinel is a function call.
Platform support
macOS arm64
Bundled
macOS amd64
Bundled
Linux amd64
Bundled
Linux arm64
Bundled
Get started with nono
Runtime safety infrastructure that works on macOS, Linux, Windows, and in CI.