Skip to main content
The learn command traces a command’s file accesses and network activity, then reports which paths and endpoints it needs. This helps you build accurate profiles without guessing. On Linux, nono learn uses strace. On macOS, it uses fs_usage for filesystem tracing and nettop for network connection discovery (both require sudo).
Linux requires strace to be installed (apt install strace or equivalent). macOS requires root privileges for fs_usage and nettop.

Basic Usage

nono learn -- my-command --with-args
nono runs the command under strace (Linux) or fs_usage + nettop (macOS), captures all file access and network activity, 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 --block-net 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

FlagDescription
--profile NAMECompare against an existing profile, showing only missing paths
--jsonOutput as JSON fragment for profile creation
--timeout SECSKill trace after N seconds (useful for long-running commands)
--allShow all accessed paths, including those covered by system defaults
--no-rdnsSkip all DNS resolution for discovered IPs
--verboseShow detailed trace output

How It Works

Linux (strace)

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>
strace -f follows forks, so network and filesystem activity from subprocesses is captured automatically. Both filesystem and network syscalls are traced in a single pass.

macOS (fs_usage + nettop)

On macOS, two tools run concurrently:
  • fs_usage -w -f filesys -f pathname traces filesystem operations (open, stat, mkdir, etc.)
  • nettop -L 0 -n -s 1 polls active network connections at 1-second intervals
fs_usage is started before the child command and filters by process name. nettop is started after the child and filters by PID, which causes it to follow the entire process tree and capture network activity from subprocesses (e.g. node spawned by a wrapper binary). nettop identifies outbound connections (Established state) and listening ports (Listen state) from its CSV output.

Processing

On both platforms, the raw trace data is then:
  1. Categorized as read, write, or readwrite (filesystem) or connect/bind (network)
  2. Filtered to remove paths already covered by base groups (system libraries, etc.)
  3. Collapsed from individual file paths to parent directories where appropriate
  4. Resolved from IP addresses to hostnames
  5. Formatted as summary or JSON output

Network Discovery

nono learn captures outbound connections and listening ports alongside filesystem accesses on both Linux and macOS. On Linux, network activity is discovered by tracing connect, bind, and sendto syscalls. On macOS, nettop polls active TCP and UDP connections.

Hostname Resolution

Captured IP addresses are resolved to hostnames. On Linux, three strategies are applied in priority order:
  1. 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.
  2. 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.
  3. 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 on Linux. On macOS, reverse DNS is used to resolve IP addresses to hostnames since nettop provides IP-level data without DNS query interception. 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 --block-net 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