Go Isolation

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.

terminal
# Sandbox a Go AI agent with read-write to the project
nono 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 only
nono 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 test
nono run --profile go-dev --allow-cwd -- go build ./...
nono run --profile go-dev --allow-cwd -- go test ./...
profiles/go-agent.json
{
"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.

main.go
package main
import (
"errors"
"fmt"
"log"
"github.com/always-further/nono-go"
)
func main() {
// 1. Build a capability set
caps := 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 accessors
err = 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.

state.go
package main
import (
"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 JSON
state, 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 JSON
restored, 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.