I gave AI agents a flight recorder
Argus runs any agent and shows you, live, everything it touches on your Mac — every file, every process, every network connection, with red flags on the paths that matter. Then I ran a real Claude Code session under it.
brew install tsuskov/tap/argus — then run any agent under it: argus -- <agent command>. MIT, macOS.
→
We've all normalized something strange: we download agents and MCP servers off the internet and hand them a shell. They run as you. Everything you can read — your SSH keys, your browser cookies, every .env you ever scattered across your projects — they can read. And if you ask yourself what an agent actually did during that twenty-minute session, the honest answer is: you scrolled past it.
This isn't hypothetical anymore. Last year someone backdoored postmark-mcp, an npm MCP server, to quietly BCC outgoing mail to an attacker's domain; security scans of public MCP servers keep finding a scary fraction with critical issues. The response so far is mostly "review the code" (nobody does) or "run it in a VM" (nobody does that either).
So I built the thing I actually wanted: a flight recorder. Not a sandbox, not a firewall — first, just the truth about what happened.
What Argus does
argus -- <agent command> starts your agent and streams every file read and write, every spawned process, every deletion and rename, and every network connection — live, filtered to that agent's process tree, so you see the agent and nothing else. Touches on sensitive paths (~/.ssh, ~/.aws, ~/.gnupg, keychains, browser cookies, .env files, .netrc, GitHub CLI credentials, your Messages database) are flagged red the moment they happen. On a terminal you get a TUI with a by-directory view; in a pipe you get lines, or JSON with --json. Every session ends with a report: argus-report.md for you, argus-report.json for your tooling.
Two minutes of Claude Code, measured
I gave Claude Code a small, ordinary task in one of my Rust repos — read the codebase, run the tests, explain the architecture — and ran it under Argus. 110 seconds, headless. The unedited summary:
Four things I did not know before I watched:
- It reads my login keychain. Twice in the session,
/usr/bin/securitypops up in the process tree and openslogin.keychain-db. Almost certainly legitimate — that's where Claude Code keeps its credentials — but I had never seen it happen, and a red line in a live stream is a very different experience from a paragraph in a docs page. That's the point of the flags: not "gotcha", but now you know what normal looks like. - Half its process tree is my own dotfiles. The agent spawns login shells, so my
.zshrcruns inside its tree: starship draws a prompt nobody sees, my WakaTime plugin logs the session,lsdaliases fire. Of the 61 processes, a good chunk is my own prompt bling — which also means every tool you've ever piled into your shellrc runs with every agent command. - The engineering is visibly careful. Config updates go through a lock file plus atomic
tmp → rename, session transcripts append after every message, an old backup gets pruned, and all 18 deletions of the session are its own temp files. A well-behaved agent under the microscope looks reassuring — that's worth knowing too, because it gives you a baseline for the day something doesn't. - Exactly two hosts. One Anthropic API address and one Google-fronted endpoint (telemetry, by the looks of it). Honest caveat from the limitations: v0.1 polls connections, so a very short-lived request could have slipped past — the v0.2 proxy funnel will make this list provably complete.
None of this is a scandal — it's the opposite. It's what your agent does every single session, invisible until you point a flight recorder at it. The interesting question is what the other tools in your stack look like under the same lens.
Watching a malicious agent work
The repo ships with a decoy — a six-step "malicious agent" that does what a compromised tool would do: read an SSH private key, read a .env, spawn children that write files, rename, delete, and phone home. It's also the test fixture; every Argus milestone had to catch all of it before it counted as done. Here's the actual session report, unedited:
Total runtime: 2.9 seconds. That's how long a rogue dependency needs to take your key and exfiltrate it — and exactly the kind of thing that's invisible in a wall of agent output and obvious in a red line.
The unedited detail is my favorite part. A six-line shell script "reads 35 files"? Yes — and the report shows they're real: curl pulls in /etc/ssl/cert.pem and openssl.cnf, the shell walks locale files, something consults the timezone database. Nothing is summarized or guessed; if a process in the tree opened it, it's in the list. The signal lives in the red lines on top.
How it works (and the trick that makes it installable)
macOS has a beautiful audit API — Endpoint Security — and a catch: consuming it normally requires a signed system extension with an Apple-granted entitlement. That's why most tools in this space are either commercial EDR or a kernel-adjacent science project.
The trick: since macOS 13, Apple ships /usr/bin/eslogger — their own signed ES client that prints events as JSON. Argus subscribes to eight event types (exec, fork, exit, open, create, write, unlink, rename) through it, follows fork/exec to maintain the agent's pid tree, and drops everything outside the tree. No kext, no entitlement, no third-party daemon — the privileged component is Apple's own binary. The cost is honest friction: eslogger needs sudo and Full Disk Access, once.
Two details I care about:
- Nothing escapes at startup. The agent launches behind a stop-gate — a tiny
shtrampoline that stops itself and onlyexecs your command once the event subscription is confirmed live. An agent can't sneak a read into the first milliseconds. - Network, honestly. Endpoint Security has no TCP events at all (I checked all 104 event types), so v0.1 polls
lsofover the pid tree every 500ms. That catches real sessions fine — but a sub-500ms connection can slip through, and the README says so instead of pretending otherwise. The proper fix is v0.2's proxy funnel (below).
War story, because these are the fun part: my first stop-gate raised SIGSTOP in pre_exec, between fork and exec. Every single run deadlocked — Command::spawn in Rust blocks on a pipe until the child execs, and my child had stopped itself right before doing that. The parent waited for the exec, the child waited for a SIGCONT the parent would never send. A sample of the hung process made it obvious in one stack trace. Moving the stop after the exec (stop yourself, then exec "$@" on continue — same pid throughout) fixed it, and waitpid(WUNTRACED) now confirms the stop before the run proceeds, so the race can't come back.
What it's not (yet)
v0.1 observes; it doesn't block. That's a deliberate first release: a recorder you actually run beats a policy engine you don't. v0.2 is the enforcement half, and the design is already fixed:
argus --policy policy.toml -- <agent>compiles your rules to a Seatbelt profile — the same sandbox mechanism agent vendors themselves use — so denials are enforced by the kernel, not by string-matching an event stream after the fact.- The sandbox allows outbound traffic only to Argus's local proxy, which logs and enforces a host allowlist. That closes the lsof polling gap for good: a connection either goes through the funnel or doesn't happen.
- Denials show up in the TUI as red
BLOCKEDlines.
Try it
brew install tsuskov/tap/argus, or build from source — it's ~1.2k lines of Rust, small enough to read in an evening, MIT-licensed. Run the decoy (argus -- sh tests/decoy-agent.sh) to see the red flags, then run it on your actual agent and read the report. I'd genuinely like to hear what your agents turn out to be doing — that's the whole point of a flight recorder.