The learn command uses strace to trace a command’s file accesses and network activity, then reports which paths and endpoints it needs. This helps you build accurate profiles without guessing.
nono learn is Linux-only. It requires strace to be installed.
Basic Usage
nono learn -- my-command --with-args
nono runs the command under strace, captures all file access and network syscalls, and produces a summary:
Read access needed:
/home/luke/.config/my-tool/config.yaml
/home/luke/data/input/
Write access needed:
/home/luke/data/output/
/tmp/my-tool-cache/
Outbound connections:
api.example.com (93.184.216.34):443 (12x)
cdn.example.com (104.18.25.1):443
Listening ports:
0.0.0.0:3000
(15 paths already covered by system defaults)
Network activity detected. Use --net-block to restrict network access.
Paths already covered by base groups (system libraries, temp directories, etc.) are filtered out by default.
Comparing Against a Profile
Use --profile to see only the paths that a profile doesn’t already cover:
nono learn --profile my-agent -- my-command
This shows the gap between what the profile grants and what the command actually needs - useful for tightening or expanding a profile.
JSON Output
Use --json to get machine-readable output suitable for adding to a profile:
nono learn --json -- my-command
{
"filesystem": {
"allow": ["/home/luke/project"],
"read": ["/home/luke/data"],
"write": ["/tmp/my-tool-cache"]
},
"network": {
"outbound": [
{"hostname": "api.example.com", "addr": "93.184.216.34", "port": 443, "count": 12}
],
"listening": [
{"addr": "0.0.0.0", "port": 3000, "count": 1}
]
}
}
Options
| Flag | Description |
|---|
--profile NAME | Compare against an existing profile, showing only missing paths |
--json | Output as JSON fragment for profile creation |
--timeout SECS | Kill trace after N seconds (useful for long-running commands) |
--all | Show all accessed paths, including those covered by system defaults |
--no-rdns | Skip all DNS resolution for discovered IPs |
--verbose | Show detailed trace output |
How It Works
Under the hood, nono learn runs:
strace -f -s 256 -e openat,open,access,stat,execve,creat,mkdir,rename,unlink,connect,bind,sendto,sendmsg -- <command>
It then:
- Parses all file access syscalls from the trace output
- Categorizes each file access as read, write, or readwrite
- Filters out paths already covered by base groups (system libraries, etc.)
- Collapses individual file paths to parent directories where appropriate
- Captures outbound connections (
connect) and listening ports (bind)
- Resolves IP addresses to hostnames using a three-tier strategy
- Produces the summary or JSON output
Network Discovery
nono learn captures outbound connections and listening ports alongside filesystem accesses. Network activity is discovered by tracing connect, bind, and sendto syscalls.
Hostname Resolution
Captured IP addresses are resolved to hostnames using three strategies, applied in priority order:
- Timing correlation — attaches the hostname from the preceding DNS query directly to each
connect() call. This handles DNS round-robin correctly because the mapping is per-PID.
- Forward DNS — resolves captured hostnames to build an IP-to-hostname map. This gives the hostname the program intended (e.g.,
google.com) rather than infrastructure names from reverse DNS.
- Reverse DNS — fallback for IPs with no other mapping.
Both direct DNS queries (sendto to port 53) and systemd-resolved (Varlink JSON protocol over Unix socket) are supported.
Use --no-rdns to skip all DNS resolution, which avoids network calls during analysis:
nono learn --no-rdns -- my-command
When network activity is detected, learn prints a hint suggesting --net-block to restrict network access at sandbox time.
Workflow
A typical workflow for creating a new profile:
# 1. Discover what paths the command needs
nono learn --json -- my-agent
# 2. Create a profile based on the output
cat > ~/.config/nono/profiles/my-agent.json << 'EOF'
{
"meta": {
"name": "my-agent",
"version": "1.0.0"
},
"filesystem": {
"read": ["/home/luke/data"],
"write": ["/tmp/my-tool-cache"],
"allow": ["/home/luke/project"]
}
}
EOF
# 3. Verify the profile covers everything
nono learn --profile my-agent -- my-agent
# Should show: "All paths covered by profile"
# 4. Run with the profile
nono run --profile my-agent -- my-agent