What Happened in There? A Tamper-Evident Audit Trail for AI Agents
How nono records every action an AI agent makes in an append-only Merkle tree the agent itself cannot reach, and lets anyone verify after the fact — with cryptographic proof — that the record was not forged, edited, or truncated.
Full article excerpt tap to expand
The problem with "trust me, bro" logs If you run an autonomous AI agent on your machine, you are giving a language model permission to open files, run commands, touch your filesystem, and reach out to the network. You know its dangerous, but you have to trust it to do the right thing. You have to trust it to tell you the truth about what it did, and quite often they are outright liars. So: what actually happened during that session? Most tooling hands you a log file. A log file is a story the program tells about itself. If the program is compromised — or if the agent has managed to write somewhere it shouldn't — the log becomes part of the attack surface. "I didn't run rm -rf $HOME" is not evidence when the same process that might have touched rm -rf $HOME is the one writing the log entry that says so. An audit trail for an untrusted process has to survive a very specific adversary: the process it is auditing. That means: The audit writer cannot be the audited process. The record has to be structured so that after-the-fact edits are detectable, not just discouraged. The record has to be bound to the binary that actually ran, not just "a command called node". A third party — not the host that produced the log — has to be able to verify all of the above. This post describes how nono does each of these, walks through the crypto well enough that "Merkle tree" and "inclusion proof" finally make sense, and shows why the combination makes nono the strongest AI audit system for agent execution today, anywhere on planet Earth (the last bit is a bold claim, but I apologise, I'm a bit biased, I promise to be objective in my analysis from now on.) nono's core architecture in one minute nono runs untrusted agent commands inside an OS-enforced sandbox (Landlock on Linux, Seatbelt on macOS). The defining structural boundary for audit operations is two processes: A supervisor (the parent) — trusted, unsandboxed, owns policy and auditing. A child — the untrusted agent, fully sandboxed before exec. The kernel mediates every boundary crossing between them. Capability requests (e.g. "the agent tried to openat(/etc/shadow)") are trapped by a seccomp BPF filter and delivered to the supervisor as seccomp-notify events. The supervisor decides; the kernel enforces. The audit trail lives entirely in the supervisor. The sandboxed child does not write its own audit log. It cannot open the audit file, it cannot ptrace the supervisor, and it has no shared memory with it. The child generates events by doing things; the supervisor is the sole recorder of what happened. A five-minute crash course in Merkle trees Before we get to what nono does with them, a short detour on the data structure that does all the work. Hashes, but structured A cryptographic hash (SHA-256 is what nono uses) takes arbitrary bytes and returns a 32-byte fingerprint. Two properties matter: Change even one bit of input, the hash changes unpredictably. You cannot (in practice) construct two different inputs that hash to the same output. bashprintf "hello world" | shasum -a 256b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9printf "hello evil" | shasum -a 256b5e70ecd9dc3fd9459eaaf2adb3a51644351b76114f5f7bd8438d0ea6c53d481 A single hash is good for proving "this exact blob was not modified." But we have many events — thousands of capability decisions per session. Hashing them all into one blob means to prove one event happened, you have to re-hand-over every event. A Merkle tree…
This excerpt is published under fair use for community discussion. Read the full article at Nono.