Security¶
agent-top is read-only and local-only. It reads the transcripts a harness already wrote and the process table the OS already keeps, and changes neither.
What the scans say¶
279 dependencies scanned against 1,243 RustSec advisories (database b50980aad8b8, 2026-09-09) by cargo-audit 0.22.2 on 2026-09-10. The same figures are published as JSON.
scripts/security_report.py runs cargo-audit against the workspace's Cargo.lock and writes the result to docs/data/security.json. This page and the sidebar are rendered from that file at build time, and CI fails if the published number disagrees with a fresh scan.
Three checks run in the Security workflow, each answering a different question:
| Check | Question | When |
|---|---|---|
cargo-deny |
Is any dependency subject to a RustSec advisory, an unexpected licence, or a source that is not crates.io? | every push and pull request, and daily |
| CodeQL | Does the code itself contain a pattern recognised as a vulnerability? | every push and pull request, and daily |
security_report.py --check |
Does the count published above still match a fresh scan? | every push and pull request, and daily |
The daily run catches what a push cannot: an advisory published against an unchanged Cargo.lock is a new vulnerability in a release that has already shipped.
The supply-chain rules are in deny.toml. Every crate must come from crates.io, with no git dependencies and no alternative registries, and carry a licence from an explicit permissive allowlist. A wildcard version requirement fails the build.
To run the same checks yourself:
cargo deny check # advisories, licences, sources, duplicate versions
cargo audit # RustSec advisories alone
mise run security # both, the way CI does
What it reads¶
The process table (through sysinfo and, on macOS, libproc, not by shelling out to ps) and the transcript files each harness already writes. From a transcript it takes metadata: usage records (token counts), tool and MCP call names, ids, timestamps, session id, model, working directory. It does not read prompt text or tool inputs. Of tool output it takes one thing: for Codex code mode, the byte length of each nested tool's output text, used to divide one wrapper call's share between the tools that ran inside it. The text is never stored, shown, exported or sent; only the number is kept. Context by source works within that boundary: what a tool result added to the prompt is priced from the token counts, not from the result. Accounting has the arithmetic.
What it never does¶
- Never writes to a transcript. The files a harness owns are never touched.
- Never signals or kills a process. An orphaned MCP server is reported with its pid, memory, age and the agent it came from, and the
killis yours. See what agent-top is not. - No shell-outs for discovery. Process and file information comes from library calls (
sysinfo,libproc), not from parsing the output ofps,lsof, or any other command. - No credentials, no provider. agent-top does not hold an API key or talk to a model.
- No
unsafe. There is nounsafeblock in either crate.
Network calls¶
There are two.
- A daily version check.
GET https://crates.io/api/v1/crates/agent-topwith a fixedUser-Agentand nothing else: no session data, no machine identifier, no query string. Cached to run at most once a day, in~/.cache/agent-top/update-check.json(or$XDG_CACHE_HOME/agent-top/update-check.json), which holds when it last checked, what it found, and which version you dismissed.AGENT_TOP_NO_UPDATE_CHECK=1turns it off. trace --endpoint <url>. Posts the OTLP document to the address typed on the command line, once. No default endpoint, no config key, no environment variable that turns it on.
Nothing else opens a connection. --json and --replay never touch the network; replay reads only the file you give it.
The one command that changes the machine¶
Accepting the upgrade prompt (u) runs a fixed command line chosen by detecting how the running binary was installed: brew update && brew upgrade agent-top, cargo binstall -y agent-top, or cargo install --locked agent-top. It is never a string built from input, and it never runs through a shell. The command is printed before it runs, and it runs only on that keypress.
The one other thing it starts: itself, in your multiplexer¶
Pressing o on a panel popup inside tmux, zellij, WezTerm or kitty runs that multiplexer's split command with agent-top's own binary path, this run's flags and the panel's subcommand as the arguments, for example tmux split-window -h -d -c <cwd> 'agent-top mcp'. The multiplexer is recognised from the variable it sets ($TMUX, $ZELLIJ, $WEZTERM_PANE, $KITTY_WINDOW_ID); the command is shown on the popup before the key is pressed; nothing goes through a shell except the single quoted string tmux requires. It starts a second agent-top and nothing else, and it changes nothing on disk. The full table is in Views and panes.
Local files it touches¶
Reads: the transcripts, the process table, and ~/.config/agent-top/prices.toml if it exists. A malformed price file is reported on stderr and ignored; the built-in prices still apply. See Prices.
Writes: the one cache file above, and the history store when you run agent-top sync. --replay and --json read and print; neither touches disk beyond the file you point at.
Reporting an issue¶
agent-top is MIT-licensed: github.com/kannandreams/agent-top. Open an issue, or, for anything that should not be public first, a private report through GitHub's Security tab.