AI agent security that makes the dangerous bits structurally impossible.
From the creator of
Sigstore
The standard for secure software attestation, used by PyPI, npm, brew, and Maven Central
Warning
This is an early alpha release that has not undergone comprehensive security audits. While we have taken care to implement robust security measures, there may still be undiscovered issues. We do not recommend using this in production until we release a stable version of 1.0.
Note
We are just wrapping up the separation of the CLI and core library. The last stable CLI release is still available on homebrew tap (version v0.5.0) and is fine to use. We will update this README with installation instructions when all library clients are ready. We plan to submit to homebrew-core, but the repo is not yet 30 days old.
AI agents get filesystem access, run shell commands, and are inherently open to prompt injection. The standard response is guardrails and policies. The problem is that policies can be bypassed and guardrails linguistically overcome.
Kernel-enforced sandboxing (Landlock/Seatbelt) blocks unauthorized access at the syscall level. Every filesystem change gets a rollback snapshot with integrity protection. Destructive commands are denied before they run. Secrets are injected without touching disk. When the agent needs access outside its permissions, a kernel-mediated supervisor intercepts the syscall via seccomp BPF, opens the file after user approval, and injects only the file descriptor — the agent never executes its own open(). No root or CAP_SYS_ADMIN required. Runs on any Linux kernel 5.13+ — bare metal, containers(Docker,Podman,K8s), Firecracker, Kata.
"nono, so secure, Chuck Norris tried to break out, but gave up and went home." — Chuck Norris (allegedly)
The CLI builds on the library to provide a ready-to-use sandboxing tool, popular with coding-agents, with built-in profiles, policy groups, and interactive UX.
# Claude Code with inbuilt profile
nono run --profile claude-code -- claude
# OpenCode with custom permissions
nono run --profile opencode --allow-cwd/src --allow-cwd/output -- opencode
# OpenClaw with custom permissions
nono run --profile openclaw --allow-cwd -- openclaw gateway
# Any command with custom permissions
nono run --read ./src --write ./output -- cargo buildThe core is a Rust library that can be embedded into any application via native bindings. The library is a policy-free sandbox primitive -- it applies only what clients explicitly request.
Rust — crates.io
use nono::{CapabilitySet, Sandbox};
let mut caps = CapabilitySet::new();
caps.allow_read("/data/models")?;
caps.allow_write("/tmp/workspace")?;
Sandbox::apply(&caps)?; // Irreversible — kernel-enforced from here on
Python — nono-py
from nono_py import CapabilitySet, AccessMode, apply
caps = CapabilitySet()
caps.allow_path("/data/models", AccessMode.READ)
caps.allow_path("/tmp/workspace", AccessMode.READ_WRITE)
apply(caps) # Apply CapabilitySet
TypeScript — nono-ts
import { CapabilitySet, AccessMode, apply } from "nono-ts";
const caps = new CapabilitySet();
caps.allowPath("/data/models", AccessMode.Read);
caps.allowPath("/tmp/workspace", AccessMode.ReadWrite);
apply(caps); // Irreversible — kernel-enforced from here onnono applies OS-level restrictions that cannot be bypassed or escalated from within the sandboxed process. Permissions are defined as capabilities granted before execution -- once the sandbox is applied, it is irreversible. All child processes inherit the same restrictions.
| Platform | Mechanism | Minimum Kernel |
|---|---|---|
| macOS | Seatbelt | 10.5+ |
| Linux | Landlock | 5.13+ |
# Grant read to src, write to output — everything else is denied by the kernel
nono run --read ./src --write ./output -- cargo buildCredentials (API keys, tokens, passwords) are loaded from the system keystore and injected into the sandboxed process as environment variables at runtime. The keystore files themselves are never exposed to the sandboxed process, preventing exfiltration of raw secrets even if the agent is compromised.
# Store a secret in the system keystore, then inject it at runtime
security add-generic-password \
-T /usr/local/bin/nono \
-s "nono" \
-a "openai_api_key" \
-w "my_super_secret_api_key"
nono run --secrets openai_api_key --allow-cwd -- agent-commandSecurity policy is defined as named groups in a single JSON file. Each group specifies allow/deny rules for filesystem paths, command execution, and platform-specific behavior. Profiles reference groups by name, making it straightforward to compose fine-grained policies from reusable building blocks. Profile-level filesystem entries and CLI overrides are applied additively on top.
Groups define reusable rules:
{
"deny_credentials": {
"description": "Block access to cryptographic keys, tokens, and cloud credentials",
"deny": {
"access": ["~/.ssh", "~/.gnupg", "~/.aws", "~/.kube", "~/.docker"]
}
},
"node_runtime": {
"description": "Node.js runtime and package manager paths",
"allow": {
"read": ["~/.nvm", "~/.fnm", "~/.npm", "/usr/local/lib/node_modules"]
}
}
}Profiles compose groups by name and add their own filesystem entries on top:
{
"claude-code": {
"security": {
"groups": ["user_caches_macos", "node_runtime", "rust_runtime", "unlink_protection"]
},
"filesystem": {
"allow": ["$HOME/.claude"],
"read_file": ["$HOME/.gitconfig"]
}
}
}Dangerous commands (rm, dd, chmod, sudo, scp, and others) are blocked before execution. Commands can be selectively allowed or additional commands blocked per invocation in accordance with user profiles.
# rm is blocked by default
$ nono run --allow-cwd -- rm -rf /
nono: blocked command: rm
# Selectively allow a blocked command
nono run --allow-cwd --allow-command rm -- rm ./temp-file.txtWarning
This feature will be reinvented at some point, as execution of dangerous commands can still pass by masking, e.g. placed inside sh -c '...', or a wrapper script. This is layered on top of the kernel sandbox as defense-in-depth, as even if a command were allowed, the sandbox would still enforce filesystem restrictions. . The current model trusts that the sandbox restrictions are the real security boundary, and not command blocking, which is more a layered defense.
nono takes content-addressable snapshots of your working directory before the sandboxed process runs. If the agent makes unwanted changes, you can interactively review and restore individual files or the entire directory to its previous state. Snapshots use SHA-256 deduplication and Merkle tree commitments for integrity verification.
# List snapshots taken during sandboxed sessions
nono rollback list
# Interactively review and restore changes
nono rollback restoreOn Linux, nono can run in supervised mode where the sandboxed process starts with minimal permissions. When the agent needs access to a file outside its sandbox, the request is intercepted via seccomp user notification and routed to the supervisor, which prompts the user for approval. Approved access is granted transparently by injecting file descriptors -- the agent never needs to know about nono. Sensitive paths (system config, SSH keys, etc.) are configured as never-grantable regardless of user approval.
# Run with rollback snapshots and capability expansion
nono run --rollback --supervised --allow-cwd -- claudeEvery sandboxed session records what command was run, when it started and ended, its exit code, tracked paths, and cryptographic snapshot commitments. Session logs can be inspected as structured JSON for compliance and forensics.
# Show audit record for a session
nono audit show 20260216-193311-20751 --json
❯ nono audit show 20260216-193311-20751 --json
{
"command": [
"sh",
"-c",
"echo done"
],
"ended": "2026-02-16T19:33:11.519810+00:00",
"exit_code": 0,
"merkle_roots": [
"2ee13961d5b9ec78cca0c2bd1bad29ea39c3b2256df00dec97978e131961b753",
"2ee13961d5b9ec78cca0c2bd1bad29ea39c3b2256df00dec97978e131961b753"
],
"session_id": "20260216-193311-20751",
"snapshots": [
{
"changes": [],
"file_count": 1,
"merkle_root": "2ee13961d5b9ec78cca0c2bd1bad29ea39c3b2256df00dec97978e131961b753",
"number": 0,
"timestamp": "1771270391"
},
{
"changes": [],
"file_count": 1,
"merkle_root": "2ee13961d5b9ec78cca0c2bd1bad29ea39c3b2256df00dec97978e131961b753",
"number": 1,
"timestamp": "1771270391"
}
],
"started": "2026-02-16T19:33:11.496516+00:00",
"tracked_paths": [
"/Users/jsmith/project"
]
}brew tap always-further/nono
brew install nonoNote
The package is not in homebrew official yet, give us a star to help raise our profile for when we request approval.
See the Installation Guide for prebuilt binaries and package manager instructions.
See the Development Guide for building from source.
nono ships with built-in profiles for popular AI coding agents. Each profile defines audited, minimal permissions.
| Client | Profile | Docs |
|---|---|---|
| Claude Code | claude-code |
Guide |
| OpenCode | opencode |
Guide |
| OpenClaw | openclaw |
Guide |
nono is agent-agnostic and works with any CLI command. See the full documentation for usage details, configuration, and integration guides.
| Project | Repository |
|---|---|
| claw-wrap | GitHub |
nono is structured as a Cargo workspace:
- nono (
crates/nono/) -- Core library. A policy-free sandbox primitive that applies only what clients explicitly request. - nono-cli (
crates/nono-cli/) -- CLI binary. Owns all security policy, profiles, hooks, and UX. - nono-ffi (
bindings/c/) -- C FFI bindings with auto-generated header.
Language-specific bindings are maintained separately:
| Language | Repository | Package |
|---|---|---|
| Python | nono-py | PyPI |
| TypeScript | nono-ts | npm |
We encourage using AI tools to contribute to nono. However, you must understand and carefully review any AI-generated code before submitting. The security of nono is paramount -- always review and test your code thoroughly, especially around core sandboxing functionality. If you don't understand how a change works, please ask for help in the Discord before submitting a PR.
If you discover a security vulnerability, please do not open a public issue. Instead, follow the responsible disclosure process outlined in our Security Policy.
Apache-2.0