Skip to content

Commit 75f4967

Browse files
authored
New docs inline with 060 (#179)
1 parent 7271d64 commit 75f4967

12 files changed

Lines changed: 496 additions & 589 deletions

File tree

AGENTS.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Agent Guide: nono
2+
3+
This repository contains the `nono` project, a capability-based sandboxing system for running untrusted AI agents.
4+
It is a Cargo workspace with three members:
5+
- `crates/nono` (core library): Pure sandbox primitive.
6+
- `crates/nono-cli` (CLI binary): Owns security policy, profiles, and UX.
7+
- `bindings/c` (C FFI): C bindings.
8+
9+
## Build, Test, and Lint Commands
10+
11+
### Primary Commands
12+
Use the `Makefile` for standard workflows:
13+
14+
- **Build All**: `make build`
15+
- **Test All**: `make test`
16+
- **Lint & Format**: `make check` (runs clippy + fmt check)
17+
- **CI Simulation**: `make ci` (runs check + test)
18+
19+
### Component-Specific Targets
20+
- **Library**: `make build-lib` / `make test-lib`
21+
- **CLI**: `make build-cli` / `make test-cli`
22+
- **FFI**: `make build-ffi` / `make test-ffi`
23+
24+
### Running a Single Test
25+
To run a specific test case, use `cargo test` directly:
26+
27+
```bash
28+
# Run a specific test in the library
29+
cargo test -p nono -- test_function_name
30+
31+
# Run a specific test in the CLI
32+
cargo test -p nono-cli -- test_function_name
33+
34+
# Run a test and show stdout (useful for debugging)
35+
cargo test -p nono -- test_function_name --nocapture
36+
```
37+
38+
## Code Style & Standards
39+
40+
### Formatting & Linting
41+
- **Strict Clippy**: We enforce `clippy::unwrap_used`. **NEVER** use `.unwrap()` or `.expect()`.
42+
- **Formatting**: Run `make fmt` to apply standard Rust formatting.
43+
- **Imports**: Group imports by crate (std, external, internal).
44+
45+
### Error Handling
46+
- **No Panics**: Libraries should almost never panic. Use `Result` for all error conditions.
47+
- **Error Type**: Use `NonoError` for all errors. Propagate using `?`.
48+
- **Must Use**: Apply `#[must_use]` to functions returning critical `Result`s.
49+
50+
### Naming Conventions
51+
- **Types/Traits**: `PascalCase` (e.g., `SandboxState`, `CapabilitySet`).
52+
- **Functions/Variables**: `snake_case` (e.g., `apply_sandbox`, `is_supported`).
53+
- **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `MAX_PATH_LENGTH`).
54+
55+
## Security Mandates (CRITICAL)
56+
57+
**SECURITY IS NON-NEGOTIABLE.** Every change must be evaluated through a security lens.
58+
59+
### Path Handling
60+
- **Canonicalization**: Always canonicalize paths at the enforcement boundary.
61+
- **Comparison**: Use `Path::components()` or `Path::starts_with()`.
62+
- **NEVER** use string operations like `str::starts_with()` for paths (vulnerable to `/home` vs `/homeevil`).
63+
- **Symlinks**: Be aware of TOCTOU (Time-of-Check Time-of-Use) race conditions.
64+
65+
### Memory & Arithmetic
66+
- **Secrets**: Use the `zeroize` crate for sensitive data (keys/passwords) in memory.
67+
- **Math**: Use `checked_`, `saturating_`, or `overflowing_` methods for security-critical arithmetic.
68+
69+
### Safe Code
70+
- **Unsafe**: Restrict `unsafe` code to FFI modules only.
71+
- **Documentation**: All `unsafe` blocks must be wrapped in `// SAFETY:` comments explaining why it is safe.
72+
73+
### Principles
74+
- **Least Privilege**: Only grant the minimum necessary capabilities.
75+
- **Fail Secure**: On any error, deny access. Never silently degrade to a less secure state.
76+
- **Explicit Over Implicit**: Security-relevant behavior must be explicit and auditable.
77+
78+
## Usage Example (Library)
79+
80+
The core library (`crates/nono`) provides the sandbox primitive. Clients must construct a `CapabilitySet` and apply it.
81+
82+
```rust
83+
use nono::{CapabilitySet, AccessMode, Sandbox};
84+
85+
fn main() -> nono::Result<()> {
86+
// Build capability set - client must add ALL paths
87+
let caps = CapabilitySet::new()
88+
.allow_path("/usr", AccessMode::Read)?
89+
.allow_path("/project", AccessMode::ReadWrite)?
90+
.block_network();
91+
92+
// Check platform support
93+
let support = Sandbox::support_info();
94+
if !support.is_supported {
95+
eprintln!("Warning: {}", support.details);
96+
}
97+
98+
// Apply sandbox - this is irreversible
99+
Sandbox::apply(&caps)?;
100+
101+
Ok(())
102+
}
103+
```
104+
105+
## Implementation Guidelines
106+
107+
### Library vs CLI
108+
- **Library (`crates/nono`)**: Policy-free. Applies *only* what is in `CapabilitySet`.
109+
- **CLI (`crates/nono-cli`)**: Defines policy (deny rules, sensitive paths).
110+
111+
### Platform Specifics
112+
- **Linux (Landlock)**: Strictly allow-list. Cannot express deny-within-allow.
113+
- **macOS (Seatbelt)**: Scheme-like DSL. Supports explicit deny rules.
114+
- **Cross-Platform**: Design abstractions that work securely on both. Test on both if possible.
115+
116+
### Common Pitfalls to Avoid
117+
1. **Silent Fallbacks**: `unwrap_or_default()` on security config returns empty permissions (no protection). Fail hard instead.
118+
2. **Broad Permissions**: Do not grant access to entire directories when specific paths suffice.
119+
3. **Environment Variables**: Validate `HOME`, `TMPDIR`, etc. before use. Do not assume they are trustworthy.
120+
4. **Dead Code**: Avoid `#[allow(dead_code)]`. Remove unused code or write tests for it.
121+
122+
## Testing Strategy
123+
When writing tests for new capabilities:
124+
1. **Unit Tests**: Verify the logic of `CapabilitySet` construction.
125+
2. **Integration Tests**: Use `tests/` directory to run actual sandbox enforcement checks.
126+
3. **Platform Checks**: Use `#[cfg(target_os = "linux")]` or `#[cfg(target_os = "macos")]` if the test is platform-specific.
127+
128+
## Quick Reference
129+
- **Check code quality**: `make clippy`
130+
- **Fix formatting**: `make fmt`
131+
- **Run all tests**: `make test`

docs/cli/clients/claude-code.mdx

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,28 @@ The built-in profile provides:
3838

3939
## Custom Profile
4040

41-
If you need different permissions, create a custom profile at `~/.config/nono/profiles/claude-code.toml`:
42-
43-
```toml
44-
[meta]
45-
name = "claude-code"
46-
version = "1.0.0"
47-
description = "Claude Code with additional project access"
48-
49-
[filesystem]
50-
allow = ["$WORKDIR", "$HOME/.claude"]
51-
read = ["$HOME/shared-libs"]
52-
53-
[filesystem.files]
54-
allow = ["$HOME/.claude.json"]
55-
read = ["$HOME/.gitconfig"]
56-
57-
[network]
58-
block = false
59-
60-
[secrets]
61-
anthropic_api_key = "ANTHROPIC_API_KEY"
41+
If you need different permissions, create a custom profile at `~/.config/nono/profiles/claude-code.json`:
42+
43+
```json
44+
{
45+
"meta": {
46+
"name": "claude-code",
47+
"version": "1.0.0",
48+
"description": "Claude Code with additional project access"
49+
},
50+
"filesystem": {
51+
"allow": ["$WORKDIR", "$HOME/.claude"],
52+
"read": ["$HOME/shared-libs"],
53+
"allow_file": ["$HOME/.claude.json"],
54+
"read_file": ["$HOME/.gitconfig"]
55+
},
56+
"network": {
57+
"block": false
58+
},
59+
"env_credentials": {
60+
"anthropic_api_key": "ANTHROPIC_API_KEY"
61+
}
62+
}
6263
```
6364

6465
**Usage:**
@@ -91,7 +92,7 @@ Then run with secrets:
9192
nono run --profile claude-code --env-credential anthropic_api_key -- claude
9293
```
9394

94-
See [Secrets Management](/cli/features/secrets) for full documentation.
95+
See [Credential Injection](/cli/features/credential-injection) for full documentation.
9596

9697
### Restrict to Specific Projects
9798

docs/cli/clients/openclaw.mdx

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,31 @@ nono has already proven capable of stopping some nasty reported CVEs from exploi
3434

3535
## Custom Profile
3636

37-
If you need different permissions, create a custom profile at `~/.config/nono/profiles/openclaw-strict.toml`:
37+
If you need different permissions, create a custom profile at `~/.config/nono/profiles/openclaw-strict.json`:
3838

3939
```bash
4040
mkdir -p ~/.config/nono/profiles
4141
```
4242

43-
```toml
44-
[meta]
45-
name = "openclaw-strict"
46-
version = "1.0.0"
47-
description = "OpenClaw with read-only config access"
48-
49-
[filesystem]
50-
allow = ["$WORKDIR"]
51-
read = ["$HOME/.openclaw", "$HOME/.config/openclaw"]
52-
53-
[network]
54-
block = false # Required for messaging APIs
55-
56-
[secrets]
57-
# Load credentials from system keystore instead of files
58-
telegram_bot_token = "TELEGRAM_BOT_TOKEN"
59-
openai_api_key = "OPENAI_API_KEY"
43+
```json
44+
{
45+
"meta": {
46+
"name": "openclaw-strict",
47+
"version": "1.0.0",
48+
"description": "OpenClaw with read-only config access"
49+
},
50+
"filesystem": {
51+
"allow": ["$WORKDIR"],
52+
"read": ["$HOME/.openclaw", "$HOME/.config/openclaw"]
53+
},
54+
"network": {
55+
"block": false
56+
},
57+
"env_credentials": {
58+
"telegram_bot_token": "TELEGRAM_BOT_TOKEN",
59+
"openai_api_key": "OPENAI_API_KEY"
60+
}
61+
}
6062
```
6163

6264
**Usage:**
@@ -65,7 +67,7 @@ nono run --profile openclaw-strict --env-credential -- openclaw gateway
6567
```
6668

6769
<Note>
68-
Custom profiles override built-in profiles of the same name. If you create `~/.config/nono/profiles/openclaw.toml`, it will be used instead of the built-in.
70+
Custom profiles override built-in profiles of the same name. If you create `~/.config/nono/profiles/openclaw.json`, it will be used instead of the built-in.
6971
</Note>
7072

7173
See [Security Profiles](/cli/features/profiles-groups) for the full profile format reference.
@@ -120,7 +122,7 @@ The secrets are loaded from the keystore and injected as `$TELEGRAM_BOT_TOKEN` a
120122
Secrets are loaded **before** the sandbox is applied, then zeroized from nono's memory after `exec()`. The sandboxed process cannot access the keystore directly - it only receives the specific secrets you authorized.
121123
</Note>
122124

123-
See [Secrets Management](/cli/features/secrets) for complete documentation on storing and managing secrets.
125+
See [Credential Injection](/cli/features/credential-injection) for complete documentation on storing and managing secrets.
124126

125127
### Limit Agent Filesystem Access
126128

docs/cli/clients/opencode.mdx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,31 @@ The built-in profile provides:
3535

3636
## Custom Profile
3737

38-
Create `~/.config/nono/profiles/opencode.toml` for different permissions:
39-
40-
```toml
41-
[meta]
42-
name = "opencode"
43-
version = "1.0.0"
44-
description = "OpenCode with restricted access"
45-
46-
[filesystem]
47-
allow = ["$WORKDIR"]
48-
read = [
49-
"$XDG_CONFIG_HOME/opencode",
50-
"$XDG_DATA_HOME/opencode"
51-
]
52-
53-
[network]
54-
block = false
55-
56-
[secrets]
57-
openai_api_key = "OPENAI_API_KEY"
38+
Create `~/.config/nono/profiles/opencode.json` for different permissions:
39+
40+
```json
41+
{
42+
"meta": {
43+
"name": "opencode",
44+
"version": "1.0.0",
45+
"description": "OpenCode with restricted access"
46+
},
47+
"workdir": {
48+
"access": "readwrite"
49+
},
50+
"filesystem": {
51+
"read": [
52+
"$XDG_CONFIG_HOME/opencode",
53+
"$XDG_DATA_HOME/opencode"
54+
]
55+
},
56+
"network": {
57+
"block": false
58+
},
59+
"secrets": {
60+
"openai_api_key": "OPENAI_API_KEY"
61+
}
62+
}
5863
```
5964

6065
**Usage:**
@@ -83,7 +88,7 @@ Then run:
8388
nono run --profile opencode --env-credential openai_api_key -- opencode
8489
```
8590

86-
See [Secrets Management](/cli/features/secrets) for full documentation.
91+
See [Credential Injection](/cli/features/credential-injection) for full documentation.
8792

8893
### Read-Only Mode
8994

docs/cli/features/atomic-rollbacks.mdx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,13 @@ Configure these in your nono config file or use `nono rollback cleanup` for manu
195195

196196
Profiles can customize rollback behavior:
197197

198-
```toml
199-
[rollback]
200-
exclude_patterns = ["node_modules", ".next", "__pycache__", "target"]
201-
exclude_globs = ["*.tmp.[0-9]*.[0-9]*"]
198+
```json
199+
{
200+
"rollback": {
201+
"exclude_patterns": ["node_modules", ".next", "__pycache__", "target"],
202+
"exclude_globs": ["*.tmp.[0-9]*.[0-9]*"]
203+
}
204+
}
202205
```
203206

204207
These exclusions are combined with gitignore patterns from the working directory.

0 commit comments

Comments
 (0)