nono

package module
v0.21.1-0...-875bc26 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

nono-go

CI Go Reference

Go CGo bindings for the nono capability-based security sandbox.

nono applies an irreversible, least-privilege sandbox to the current process using Linux Landlock (Linux) or Seatbelt/sandbox_init (macOS). You declare the paths and network modes the process needs; nono enforces them at the kernel level.

Platform support

OS Arch Library bundled?
macOS arm64 yes
macOS amd64 yes
Linux amd64 yes
Linux arm64 yes

All platforms work out of the box. The static libraries for all four targets are bundled in the repository.

Prerequisites

  • Go 1.24+
  • A C toolchain (gcc or clang) for CGo

Installation

go get github.com/nolabs-ai/nono-go

Building native libraries

The bundled libraries are built from the upstream nono repository using scripts/build-libs.sh. Run this script when you want to update the bundled libraries to a newer nono upstream commit.

Requirements: cargo (for Apple targets), Docker (for Linux targets — uses rust:latest via emulation)

# Clone nono automatically and build all targets
./scripts/build-libs.sh

# Use an existing nono checkout
./scripts/build-libs.sh --nono-src /path/to/nono

Bundled library provenance is recorded in internal/clib/MANIFEST.json. After rebuilding libraries locally, run make clib-manifest-update; CI verifies the manifest with make clib-manifest.

Testing

make ci          # local hygiene, tests, coverage, race tests, staticcheck
make test-apply  # require the irreversible Apply subprocess test to pass

Usage

Apply a sandbox
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)
}

// Irreversible — applies to this process and all children.
if err := nono.Apply(caps); err != nil {
    log.Fatal(err)
}
Query permissions without applying

QueryContext lets you check what a capability set would allow before (or instead of) applying it. The capability set is cloned internally, so later changes to caps don't affect the query context.

caps := nono.New()
if err := caps.AllowPath("/home/user/data", nono.AccessRead); err != nil {
    log.Fatal(err)
}
if err := caps.SetNetworkMode(nono.NetworkAllowAll); err != nil {
    log.Fatal(err)
}

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

netResult, err := qc.QueryNetwork()
if err != nil {
    log.Fatal(err)
}
fmt.Println(netResult.Status) // nono.QueryAllowed
Serialize and deserialize state

SandboxState provides a JSON-serializable snapshot of a CapabilitySet, useful for persisting or transmitting sandbox configuration.

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)
}

state, err := nono.StateFromCaps(caps)
if err != nil {
    log.Fatal(err)
}
defer state.Close()

jsonStr, err := state.JSON()
if err != nil {
    log.Fatal(err)
}

// Later: restore from JSON
restored, err := nono.StateFromJSON(jsonStr)
if err != nil {
    log.Fatal(err)
}
defer restored.Close()

caps2, err := restored.Caps()
if err != nil {
    log.Fatal(err)
}
defer caps2.Close()

Error handling

All failing operations return *nono.Error. Use errors.Is with a sentinel error to test for specific failure kinds:

err := caps.AllowPath("/nonexistent", nono.AccessRead)
if errors.Is(err, nono.ErrPathNotFound) {
    // path does not exist
}

Named sentinel errors: ErrPathNotFound, ErrExpectedDirectory, ErrExpectedFile, ErrPathCanonicalization, ErrNoCapabilities, ErrSandboxInit, ErrUnsupportedPlatform, ErrBlockedCommand, ErrConfigParse, ErrProfileParse, ErrIO, ErrInvalidArg, ErrTrustVerification, ErrUnknown.

macOS path canonicalization

On macOS, paths under /var (including those returned by os.TempDir and t.TempDir()) are symlinks to /private/var. nono canonicalizes paths, so the resolved capability will be under /private/var. When checking PathCovered, resolve symlinks first:

dir, _ := filepath.EvalSymlinks(t.TempDir())
covered, err := caps.PathCovered(filepath.Join(dir, "file.txt"))

Documentation

Overview

Package nono provides Go bindings for the nono capability-based security sandbox.

nono uses Linux Landlock and macOS Seatbelt to restrict what resources a process can access. The three primary types are:

  • CapabilitySet — builds the set of allowed filesystem paths and network modes.
  • QueryContext — checks whether a given path or network operation would be permitted.
  • SandboxState — serialises and deserialises a CapabilitySet as JSON.

Typical usage:

caps := nono.New()
defer caps.Close()
if err := caps.AllowPath("/data", nono.AccessRead); err != nil {
    log.Fatal(err)
}
if err := caps.SetNetworkMode(nono.NetworkBlocked); err != nil {
    log.Fatal(err)
}
if err := nono.Apply(caps); err != nil { // irreversible
    log.Fatal(err)
}
Example (ErrorHandling)
package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/nolabs-ai/nono-go"
)

func main() {
	dir, err := os.MkdirTemp("", "nono-example-*")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	caps := nono.New()
	defer caps.Close()

	err = caps.AllowPath(filepath.Join(dir, "missing"), nono.AccessRead)
	fmt.Println(errors.Is(err, nono.ErrPathNotFound))

}
Output:
true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrPathNotFound         error = &Error{code: ErrCodePathNotFound, message: "path not found"}
	ErrExpectedDirectory    error = &Error{code: ErrCodeExpectedDirectory, message: "expected directory"}
	ErrExpectedFile         error = &Error{code: ErrCodeExpectedFile, message: "expected file"}
	ErrPathCanonicalization error = &Error{code: ErrCodePathCanonicalization, message: "path canonicalization failed"}
	ErrNoCapabilities       error = &Error{code: ErrCodeNoCapabilities, message: "no capabilities"}
	ErrSandboxInit          error = &Error{code: ErrCodeSandboxInit, message: "sandbox init failed"}
	ErrUnsupportedPlatform  error = &Error{code: ErrCodeUnsupportedPlatform, message: "unsupported platform"}
	ErrBlockedCommand       error = &Error{code: ErrCodeBlockedCommand, message: "blocked command"}
	ErrConfigParse          error = &Error{code: ErrCodeConfigParse, message: "config parse error"}
	ErrProfileParse         error = &Error{code: ErrCodeProfileParse, message: "profile parse error"}
	ErrIO                   error = &Error{code: ErrCodeIO, message: "i/o error"}
	ErrInvalidArg           error = &Error{code: ErrCodeInvalidArg, message: "invalid argument"}
	ErrTrustVerification    error = &Error{code: ErrCodeTrustVerification, message: "trust verification failed"}
	ErrUnknown              error = &Error{code: ErrCodeUnknown, message: "unknown error"}
)

Sentinel errors for use with errors.Is. Example:

if errors.Is(err, nono.ErrPathNotFound) { ... }

The Error.Is method matches by code only, so the message from the C library does not need to match the sentinel's message. Both fields of Error are unexported, so callers cannot mutate sentinel values through an errors.As pointer — the error codes are stable for the lifetime of the process.

Functions

func Apply

func Apply(caps *CapabilitySet) error

Apply activates the sandbox for the current process using the given capabilities. This is irreversible — once applied, the process and all children can only access resources allowed by caps.

On success caps is closed: the capability set is consumed by the kernel and any subsequent mutations would have no effect on the active sandbox.

func IsSupported

func IsSupported() bool

IsSupported reports whether sandboxing is available on the current platform.

func Version

func Version() string

Version returns the nono library version string.

Types

type AccessMode

type AccessMode uint32

AccessMode controls the type of filesystem access granted. uint32 matches the C parameter type (uint32_t) used in the FFI functions.

func (AccessMode) String

func (m AccessMode) String() string

String returns a human-readable representation of the access mode.

type CapabilitySet

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

CapabilitySet holds a set of permissions to be applied to the sandbox. Use New to create one, and Apply to activate it. Call CapabilitySet.Close when done if you want immediate cleanup; otherwise the finalizer will release memory when GC runs.

CapabilitySet is safe for concurrent use. Close may be called concurrently with other methods; subsequent calls on a closed set return an error. Read-only methods (NetworkMode, ProxyPort, IsNetworkBlocked, Summary, PathCovered, FSCapabilities) may execute concurrently with each other.

func New

func New() *CapabilitySet

New creates a new empty CapabilitySet. It panics on allocation failure (equivalent to the runtime panicking on out-of-memory).

func (*CapabilitySet) AddPlatformRule

func (cs *CapabilitySet) AddPlatformRule(rule string) error

AddPlatformRule adds a raw platform-specific sandbox rule. On macOS this is a Seatbelt S-expression; on Linux it is ignored.

func (*CapabilitySet) AllowCommand

func (cs *CapabilitySet) AllowCommand(cmd string) error

AllowCommand adds a command to the allow list (overrides block lists).

func (*CapabilitySet) AllowFile

func (cs *CapabilitySet) AllowFile(path string, mode AccessMode) error

AllowFile grants single-file access at the given path with the specified mode.

func (*CapabilitySet) AllowPath

func (cs *CapabilitySet) AllowPath(path string, mode AccessMode) error

AllowPath grants directory access at the given path with the specified mode.

func (*CapabilitySet) BlockCommand

func (cs *CapabilitySet) BlockCommand(cmd string) error

BlockCommand adds a command to the block list.

func (*CapabilitySet) Close

func (cs *CapabilitySet) Close() error

Close frees the underlying C capability set immediately. It is safe to call Close multiple times; subsequent calls are no-ops. Close always returns nil; the error return satisfies io.Closer.

func (*CapabilitySet) Deduplicate

func (cs *CapabilitySet) Deduplicate() error

Deduplicate removes redundant filesystem capabilities, keeping the highest access level for overlapping paths. Returns an error only if the set is closed; for open sets, the operation always succeeds (the underlying C function returns void and cannot fail).

func (*CapabilitySet) FSCapabilities

func (cs *CapabilitySet) FSCapabilities() []FSCapability

FSCapabilities returns all filesystem capabilities in the set. Returns nil if the set is closed. Entries whose access mode is reported as invalid by the C library (NONO_ACCESS_MODE_INVALID) are skipped.

Warning: this method holds the internal read lock across O(N × fields) sequential CGo calls — one per field per entry — because the C API has no bulk accessor. All write operations (AllowPath, AllowFile, Close, etc.) are blocked for the full duration of the iteration. On large capability sets this may cause write starvation; call CapabilitySet.Deduplicate beforehand to minimize N.

TODO(perf): replace with a single CGo call when a bulk C accessor is available upstream.

func (*CapabilitySet) IsNetworkBlocked

func (cs *CapabilitySet) IsNetworkBlocked() bool

IsNetworkBlocked reports whether outbound network access is blocked. Returns false if the set is closed.

Note: although [SetNetworkBlocked] is deprecated in favour of [SetNetworkMode], this read-only accessor is retained because NetworkMode() == NetworkBlocked is less readable in simple boolean contexts.

func (*CapabilitySet) NetworkMode

func (cs *CapabilitySet) NetworkMode() NetworkMode

NetworkMode returns the current network mode. Returns NetworkBlocked if the set is closed.

func (*CapabilitySet) PathCovered

func (cs *CapabilitySet) PathCovered(path string) (bool, error)

PathCovered reports whether path is covered by an existing directory capability. Returns (false, ErrInvalidArg) if path contains a NUL byte (which would be silently truncated by C.CString, making the check meaningless). Returns (false, nil) if the set is closed.

func (*CapabilitySet) ProxyPort

func (cs *CapabilitySet) ProxyPort() uint16

ProxyPort returns the proxy port (meaningful only in NetworkProxyOnly mode). Returns 0 if the set is closed.

func (*CapabilitySet) SetNetworkBlocked deprecated

func (cs *CapabilitySet) SetNetworkBlocked(blocked bool) error

SetNetworkBlocked sets whether outbound network access is blocked.

Deprecated: use [SetNetworkMode] instead, which provides access to all network modes. SetNetworkBlocked(true) is equivalent to SetNetworkMode(NetworkBlocked); SetNetworkBlocked(false) is equivalent to SetNetworkMode(NetworkAllowAll). The last call between SetNetworkBlocked and SetNetworkMode wins.

func (*CapabilitySet) SetNetworkMode

func (cs *CapabilitySet) SetNetworkMode(mode NetworkMode) error

SetNetworkMode sets the network mode. Use [SetProxyPort] to configure the port when using NetworkProxyOnly. SetNetworkMode and the deprecated [SetNetworkBlocked] both control the same underlying network mode field; the last call wins.

func (*CapabilitySet) SetProxyPort

func (cs *CapabilitySet) SetProxyPort(port uint16) error

SetProxyPort sets the proxy port for NetworkProxyOnly mode.

func (*CapabilitySet) Summary

func (cs *CapabilitySet) Summary() string

Summary returns a plain-text summary of the capability set. Returns an empty string if the set is closed.

type CapabilitySourceTag

type CapabilitySourceTag int

CapabilitySourceTag identifies where a capability came from. int reflects the default C enum signedness; values are always non-negative.

func (CapabilitySourceTag) String

func (s CapabilitySourceTag) String() string

String returns a human-readable representation of the capability source tag.

type Error

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

Error is returned by nono FFI calls that fail. Use Error.Code to identify the failure kind; compare against the ErrCode* constants or use errors.Is with the named Err* sentinels. Use Error.Message to obtain the human-readable description.

Both fields are unexported to prevent callers from mutating sentinel error values through an errors.As pointer, which would corrupt future errors.Is comparisons. Use the Code() and Message() accessor methods to read them.

func (*Error) Code

func (e *Error) Code() ErrorCode

Code returns the numeric error code.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface. The string is the C library's description. When no message is available the code is included instead.

func (*Error) Is

func (e *Error) Is(target error) bool

Is reports whether e has the same error code as target, enabling errors.Is checks with the named sentinel errors:

errors.Is(err, nono.ErrPathNotFound)

func (*Error) Message

func (e *Error) Message() string

Message returns the human-readable error description from the C library.

type ErrorCode

type ErrorCode int

ErrorCode is the numeric code returned by a failing nono FFI call. All failure codes are negative; zero indicates success (NONO_ERROR_CODE_OK). Codes are not guaranteed to be contiguous — use errors.Is with the named Err* sentinels or switch on ErrCode* constants rather than range-checking.

Error code constants corresponding to the C library's NonoErrorCode enum. Values are sourced directly from the C enum to prevent silent divergence if the C library renumbers any code.

type FSCapability

type FSCapability struct {
	OriginalPath string
	ResolvedPath string
	Access       AccessMode
	IsFile       bool
	Source       CapabilitySourceTag
	GroupName    string // non-empty only when Source == SourceGroup
}

FSCapability describes a single filesystem capability in a CapabilitySet.

type NetworkMode

type NetworkMode uint32

NetworkMode controls outbound network access. uint32 matches the C parameter type (uint32_t) used in the FFI functions.

func (NetworkMode) String

func (m NetworkMode) String() string

String returns a human-readable representation of the network mode.

type PlatformInfo

type PlatformInfo struct {
	Platform string
	Details  string
}

PlatformInfo describes sandboxing support on the current platform.

func SupportInfo

func SupportInfo() PlatformInfo

SupportInfo returns detailed platform sandboxing support information. When only a boolean is needed, prefer the cheaper IsSupported function, which avoids the two C-to-Go string copies (platform, details) that SupportInfo performs.

type QueryContext

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

QueryContext evaluates permission queries against a snapshot of a CapabilitySet. The capability set is cloned internally when creating a QueryContext, so subsequent modifications to the source CapabilitySet do not affect it.

QueryContext is safe for concurrent use. Multiple goroutines may call QueryPath and QueryNetwork simultaneously; only Close requires exclusive access.

Example
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/nolabs-ai/nono-go"
)

func main() {
	dir, err := os.MkdirTemp("", "nono-example-*")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	caps := nono.New()
	defer caps.Close()
	if err := caps.AllowPath(dir, nono.AccessRead); err != nil {
		panic(err)
	}
	if err := caps.SetNetworkMode(nono.NetworkAllowAll); err != nil {
		panic(err)
	}

	qc, err := nono.NewQueryContext(caps)
	if err != nil {
		panic(err)
	}
	defer qc.Close()

	pathResult, err := qc.QueryPath(filepath.Join(dir, "file.txt"), nono.AccessRead)
	if err != nil {
		panic(err)
	}
	networkResult, err := qc.QueryNetwork()
	if err != nil {
		panic(err)
	}

	fmt.Println(pathResult.Status)
	fmt.Println(networkResult.Status)

}
Output:
allowed
allowed

func NewQueryContext

func NewQueryContext(caps *CapabilitySet) (*QueryContext, error)

NewQueryContext creates a QueryContext from a CapabilitySet.

func (*QueryContext) Close

func (qc *QueryContext) Close() error

Close frees the underlying C query context immediately. It is safe to call Close multiple times; subsequent calls are no-ops. Close always returns nil; the error return satisfies io.Closer.

func (*QueryContext) QueryNetwork

func (qc *QueryContext) QueryNetwork() (QueryResult, error)

QueryNetwork checks whether outbound network access is permitted.

func (*QueryContext) QueryPath

func (qc *QueryContext) QueryPath(path string, mode AccessMode) (QueryResult, error)

QueryPath checks whether path access with the given mode is permitted.

type QueryReason

type QueryReason int

QueryReason explains why a query was allowed or denied.

func (QueryReason) String

func (r QueryReason) String() string

String returns a human-readable representation of the query reason.

type QueryResult

type QueryResult struct {
	// Status is whether the operation is allowed or denied.
	Status QueryStatus
	// Reason is the specific reason for the status.
	Reason QueryReason
	// GrantedPath is the capability path that grants access.
	// Non-empty only when Reason == ReasonGrantedPath.
	GrantedPath string
	// GrantedAccess is the access mode string of the granting capability.
	// Non-empty only when Reason == ReasonGrantedPath.
	// Typed as string because the C library returns a human-readable mode name.
	GrantedAccess string
	// ActualAccess is the access mode that was actually granted.
	// Non-empty only when Reason == ReasonInsufficientAccess.
	ActualAccess string
	// RequestedAccess is the access mode that was requested but not granted.
	// Non-empty only when Reason == ReasonInsufficientAccess.
	RequestedAccess string
}

QueryResult holds the outcome of a permission query. The C NonoQueryResult struct uses abbreviated field names that differ from the Go field names; see extractQueryResult in query.go for the mapping.

type QueryStatus

type QueryStatus int

QueryStatus indicates whether a queried operation is allowed or denied.

func (QueryStatus) String

func (s QueryStatus) String() string

String returns a human-readable representation of the query status.

type SandboxState

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

SandboxState is a serializable snapshot of a CapabilitySet. Use StateFromCaps or StateFromJSON to create one.

SandboxState is safe for concurrent use. Multiple goroutines may call JSON concurrently; only Close requires exclusive access.

Example
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/nolabs-ai/nono-go"
)

func main() {
	dir, err := os.MkdirTemp("", "nono-example-*")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	caps := nono.New()
	defer caps.Close()
	if err := caps.AllowPath(dir, nono.AccessReadWrite); err != nil {
		panic(err)
	}
	if err := caps.SetNetworkMode(nono.NetworkBlocked); err != nil {
		panic(err)
	}

	state, err := nono.StateFromCaps(caps)
	if err != nil {
		panic(err)
	}
	defer state.Close()

	jsonStr, err := state.JSON()
	if err != nil {
		panic(err)
	}

	restored, err := nono.StateFromJSON(jsonStr)
	if err != nil {
		panic(err)
	}
	defer restored.Close()

	restoredCaps, err := restored.Caps()
	if err != nil {
		panic(err)
	}
	defer restoredCaps.Close()

	fmt.Println(json.Valid([]byte(jsonStr)))
	fmt.Println(restoredCaps.NetworkMode())

}
Output:
true
blocked

func StateFromCaps

func StateFromCaps(caps *CapabilitySet) (*SandboxState, error)

StateFromCaps creates a SandboxState snapshot from a CapabilitySet.

func StateFromJSON

func StateFromJSON(data string) (*SandboxState, error)

StateFromJSON deserializes a SandboxState from a JSON string. On failure the returned error contains the C library's description. The error code is always ErrCodeUnknown because the C function does not return a numeric code; use errors.Is(err, nono.ErrUnknown) to match the error, and err.(*nono.Error).Message() for the human-readable reason.

The parameter type is string (not []byte) to match the C FFI signature, which accepts a null-terminated C string.

func (*SandboxState) Caps

func (ss *SandboxState) Caps() (*CapabilitySet, error)

Caps converts the state back to a CapabilitySet. On failure, error codes are:

  • ErrInvalidArg if the state has been closed.
  • ErrUnknown for C-originated failures (the C function returns only a pointer; use err.(*Error).Message() for the human-readable reason).

func (*SandboxState) Close

func (ss *SandboxState) Close() error

Close frees the underlying C sandbox state immediately. It is safe to call Close multiple times; subsequent calls are no-ops. Close always returns nil; the error return satisfies io.Closer.

func (*SandboxState) JSON

func (ss *SandboxState) JSON() (string, error)

JSON serializes the state to a JSON string. The return type is string (not []byte) to match the C FFI, which returns a null-terminated string that is copied into Go memory by goString. On failure, error codes are:

  • ErrInvalidArg if the state has been closed.
  • ErrUnknown for C-originated failures (the C function returns only a pointer; use err.(*Error).Message() for the human-readable reason).

Jump to

Keyboard shortcuts

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