---
title: "How to Audit a Local MCP Server on Linux: The Tool Call Is the Request, Not the Record (2026)"
description: "An MCP server on the stdio transport is a subprocess your agent launches, running as your user with your full filesystem and network access. Your agent's log records the tool call, not the syscalls the server ran. Here is how to audit one on Linux in 2026, with claudefeed and exectop."
date: 2026-09-04
authors: Necco Ceresani
canonical: https://yeet.cx/topical-takes/audit-local-mcp-server-linux
---
*Last updated: September 2026*

> **TL;DR.** On the stdio transport, the MCP specification says "the client launches the MCP server as a subprocess," and Claude Code's docs say stdio servers "run as local processes on your machine." That means an MCP server you installed from npm runs as your user, with your filesystem and network access, and your agent's transcript records the tool call it *requested* rather than the syscalls the server actually made. Nothing in the MCP protocol confines a local server. To see what one really does, watch its process subtree from the kernel: `yeet run github:yeet-src/claudefeed -- --match=node`.

I write eBPF tooling at [yeet](https://yeet.cx/docs/?utm_source=blog&utm_medium=post&utm_campaign=claudefeed), and the tools I build for watching agent sessions kept surfacing the same thing once people pointed them at MCP: a server they had installed weeks earlier was opening files nobody expected it to touch. I am not going to tell you which MCP servers are safe, because that list would be wrong by the time you read it and I do not audit npm packages for a living. What I can tell you is where the record lives. An agent's tool-call log and a server's actual behavior are two different accounts, and only one of them is produced by the kernel.

## Why doesn't my agent's transcript show the files an MCP server opened?

No. It shows the tool call your agent sent and the result it got back, which is a record of the conversation between two processes and not a record of what the second one did. When your agent calls a `search_files` tool, the transcript holds the request parameters and the JSON response. Between those two entries the server process opened files, possibly spawned a shell, possibly made an HTTPS request, and none of that appears anywhere in the transcript because the transcript is written by the client and the client cannot see inside the subprocess it launched.

This is a different gap from the one in agent sandboxing, and the difference is worth being exact about. There, the issue is that a sandbox governs Bash subprocesses while the agent's own file tools go through a permission layer. Here, the issue is that an MCP server is a **separate process with its own syscalls**, and the agent's permission system sits between the model and the tool call rather than between the server and the kernel. Approving `search_files` approves the request. It does not constrain what the process does while servicing it, and there is no protocol-level mechanism that would.

The MCP specification is explicit about the process model, which is what makes the consequence unambiguous. The stdio transport page states that "the client launches the MCP server as a subprocess" and that the two communicate over that subprocess's standard streams, with the client reading newline-delimited JSON-RPC from its `stdout`. Claude Code's MCP documentation puts the operational version of the same fact plainly: "Stdio servers run as local processes on your machine. They're ideal for tools that need direct system access or custom scripts." Direct system access is the feature. It is also the audit gap.

## Can a server in .mcp.json read ~/.ssh and my environment variables?

Yours, in full, because it is a child process of your agent and inherits your user's ambient authority. There is no sandbox in the MCP protocol, no capability negotiation that restricts filesystem or network reach, and no manifest declaring what a server will touch. A server that advertises one read-only tool has exactly the same access as one that advertises a shell. The tool list is an interface description, not a permission boundary, and treating it as the latter is the central mistake in how teams evaluate these things.

Four consequences follow, and they are the reason a kernel-side record is worth having:

- **The environment comes along.** A subprocess inherits the parent's environment by default, so tokens exported in the shell that launched your agent are visible to every stdio server it spawns. Claude Code also sets `CLAUDE_PROJECT_DIR` in the spawned server's environment so the server can resolve project-relative paths, which is a convenience and also a hint about how much context a server is handed for free.
- **A server can spawn anything.** Nothing stops a server process from calling `execve`. A tool that describes itself as a formatter can shell out, and the resulting process is a grandchild of your agent that appears in no MCP-level log.
- **Configuration can execute shell commands directly.** Claude Code's docs note that it "executes a `headersHelper` as an arbitrary shell command," gated on accepting the trust dialog for the project directory the server is declared in. Until you trust the folder, it connects with static headers alone. That gate is real, and it means a `.mcp.json` in a repository is a file that can carry executable intent.
- **Trust is the documented control.** The vendor's guidance is not a technical boundary but a judgment: "Verify you trust each server before connecting it. Servers that fetch external content can expose you to prompt injection risk." That is honest and it is also the whole enforcement story at the protocol level.

## Is npx -y for an MCP server as risky as an unreviewed npm dependency?

Because installing one looks like editing a config file and not like adding a dependency, and the review habits people apply to each are completely different. A new npm package in `package.json` goes through a pull request, and somebody at least glances at the name. A new entry in `.mcp.json` is three lines that end up on a laptop, often pasted from a README or a social post, running a package the reader has never looked at. The install command is frequently `npx -y some-mcp-server`, which fetches and executes the latest published version at launch with no lockfile and no pinned hash.

That combination is a supply-chain shape the ecosystem already has hard-won lessons about, arriving in a place where none of the tooling followed. There is no `npm audit` for MCP servers, no signature requirement, and no convention that a server declares its filesystem scope. The `-y` in that command suppresses the one prompt that would have told you a package was being fetched. None of this is exotic and all of it is ordinary practice right now, which is why the useful move is not a policy but a measurement: run the server, watch what it does, and decide with the list in front of you.

## How do I log every file and TCP connection a stdio server makes?

Watch the server's process subtree from the kernel while you exercise the tool you care about, because the kernel is the only observer that sees the server's syscalls rather than its JSON-RPC replies. Two tools cover the two halves of the question, and both take one command.

The routes differ less in power than in what they scope to, and scope is the whole difficulty with a server your agent launched at a moment you did not choose.

| Route | Scopes to | Catches a spawned grandchild | Needs a PID up front | Shows files, execs and network |
| --- | --- | --- | --- | --- |
| [claudefeed](https://github.com/yeet-src/claudefeed) | A process subtree, matched by name | Yes, the set propagates at `exec` | No, it finds live sessions itself | All three, plus listening sockets |
| [exectop](https://github.com/yeet-src/exectop) | One application's tree, through `fork` | Yes, followed in the kernel | No, launch or pid or container | Execs only, folded and ranked |
| `strace -f` | One PID you name | Yes, with `-f` | Yes, and the server starts without you | Syscalls, at a real slowdown |
| `auditd` | The whole machine, filtered after | Yes | No | All three, in a system-wide log |
| The agent's own transcript | Tool calls | No, it sees no syscalls | No | Neither; requests and replies only |

For the full picture across files, execs and network, [claudefeed](https://github.com/yeet-src/claudefeed) streams five event classes gated on a process subtree:

```sh
curl -fsSL https://yeet.cx | sh                          # install the yeet daemon, once
yeet run github:yeet-src/claudefeed -- --match=node      # most stdio servers are node
```

`--match=<name>` sets the program-name needle, tested against the exec'd basename as a case-insensitive prefix and truncated to 15 characters before it reaches the kernel. It is never matched against the full command line, so a process that merely mentions the needle in an argument cannot masquerade as a session. Match on `node` for a JavaScript server, `python` or `uv` for a Python one, and check the actual basename with `ps -eo comm` if the header reports zero seeded processes. Flags go after `--` so the runtime routes them to the script instead of consuming them itself, which is the most common first-run mistake.

The output is one line per event, append-only, newest at the bottom, with no full-screen repaint, so it is safe to pipe or redirect and colors no-op to plain text off a TTY. That makes the audit reproducible:

```sh
yeet run github:yeet-src/claudefeed -- --match=node --secs=60 > mcp-audit.log
yeet run github:yeet-src/claudefeed -- --match=node --only=exec,conn   # skip the noisy opens
```

`--secs=60` bounds the run so it exits on its own, which is what makes this scriptable from CI or from an agent. `--only=exec,conn` drops the file-open class, which is by far the noisiest, and leaves a readable narrative of what the server ran and what it dialed. Run the tool you want to audit two or three times during the window, then read the log.

### exectop: when the question is only what it launched

[exectop](https://github.com/yeet-src/exectop) answers a narrower question and answers it better, which matters when the server you are auditing spawns a lot. It follows one application's process tree through `fork` in the kernel using the `sched_process_exec`, `sched_process_fork` and `sched_process_exit` tracepoints, then folds repetition into one row per kind and ranks the commands that do not fit the pattern above the rest.

The folding is the point. A server that shells out in a loop produces hundreds of identical lines in a flat feed, and a single unexpected `curl` is one more line going past. Collapsed to eight rows, an outlier is legible as one. The README's own illustration is a build where 425 execs fold into rows led by `echo ×124` and `date ×90`, with four flagged commands ranked above them. Point it at a shell and everything that shell starts:

```sh
yeet run gh:yeet-src/exectop -- --pid $$     # watch this shell and its children
```

Then launch your agent from that shell, exercise the MCP tool, and read the flagged rows. What you are looking for is any exec you cannot map to the tool's stated job.

### Reading the output: four things worth flagging

The record is only useful if you know what counts as surprising, and for MCP servers the list is short and specific:

1. **Opens outside the project directory.** A server whose job is reading your repository has no reason to touch `~/.ssh`, `~/.aws`, `~/.config`, or a sibling checkout. This is the highest-signal class and it is why the noisy `open` events are worth keeping for at least one run.
2. **Execs that are not the server's own toolchain.** A formatter invoking a formatter is fine. A formatter invoking `curl`, `base64`, `chmod`, or a shell is a question, and `exectop` ranks exactly these above the folded ordinary rows.
3. **Connections to hosts that are not the server's stated API.** The `conn` class reads the destination address and port straight off `struct sock`, so it records where the connection actually went and not what a hostname claimed.
4. **Listening sockets.** A stdio server has no reason to listen on anything: its entire transport is `stdin` and `stdout`. A `listen` event from a stdio server is worth understanding before you keep using it.

## Can I audit a remote server on the Streamable HTTP transport?

Not usefully, and the reason is that a remote server's work does not happen on your machine. On the Streamable HTTP transport, each message is an HTTP POST to a single MCP endpoint, so what a kernel probe on your host sees is one TLS connection to the server's domain and nothing about the file reads or commands happening on the far side. The process-subtree tools in this post have nothing to attach to.

That changes what you can verify rather than eliminating the question. For a remote server the audit surface is the network boundary, meaning which endpoint it talks to and what you send it, and beyond that you are relying on the operator's own controls and whatever contract you have with them. The tradeoff runs in both directions and it is worth naming plainly: a remote server cannot read your `~/.ssh` because it is not on your machine, and a local one cannot quietly retain your data on someone else's infrastructure because it is. Neither is strictly safer. They fail differently, and the local case is the one where you have a kernel to ask.

## How do I sandbox an MCP server to one directory with agent-lock?

Yes, and the enforcement options are the same layers that confine any process, because an MCP server is not special. Watching tells you what happened; confinement decides what may happen, and for a server you have already decided to distrust, the second is what you want. Four routes, in rough order of how much friction they add:

- **A BPF LSM path boundary.** [agent-lock](https://github.com/yeet-src/agent-lock) attaches an eBPF program to `lsm/file_open` and returns `-EPERM` for any open outside a directory you name, propagating membership at `sched_process_fork` so the whole subtree is covered. Because a server is a child of the agent, jailing the agent covers the server. Its own limits apply: matching is by process name and an unenrolled process runs unconfined, path enforcement allows a hardlink out of the jail, and it needs `CONFIG_BPF_LSM=y` with `bpf` in the active LSM list.
- **Landlock self-confinement.** [agent-jail](https://github.com/yeet-src/agent-jail) takes the unprivileged route and enforces on the resolved inode, which closes the hardlink case at the cost of the live view. Landlock needs no BPF prerequisite, which makes it the option on a machine you do not control.
- **A container for the server alone.** Run the server in its own container with only the project volume mounted and no egress route. This is the strongest confinement per unit of thought, and it costs you the wiring: the server has to reach whatever it legitimately needs, and each pass-through is a hole in the boundary you just built.
- **Not installing it.** Worth stating because it is the option people skip past. A server whose job you could accomplish with two lines in a skill or a script does not need to be a long-lived subprocess with your full ambient authority.

The filesystem side of this is covered in depth in [how to sandbox an AI coding agent on Linux](/topical-takes/sandbox-ai-coding-agent-linux), including why seccomp cannot express a path rule and how the symlink and hardlink cases differ. This post does not re-answer it.

## Why does strace -f miss a server my agent launched?

Because `strace -f` needs a PID you name up front, and your agent launches the server at a moment you do not choose, so by the time you have the PID the interesting work is often done. Both `strace` and `auditd` work, and both are the wrong shape for a moving process tree, which is the specific difficulty an MCP server presents. The tree also moves: the agent spawns the server, the server spawns a shell, the shell spawns `git`. `auditd` catches all of it and hands you a system-wide log to filter afterward, which is the right artifact for compliance and a poor one for answering a question in the next thirty seconds.

The kernel-side difference is where the filtering happens. In `claudefeed` a `tracked` hash map of session tgids gates the file and network probes, so a system-wide firehose of `openat` never crosses the kernel boundary, and the set self-propagates across `exec` so a `git` three levels deep stays in scope without being named. Unlike `ptrace`, these probes impose no stop on the traced process, so the server runs at normal speed while you watch it. The full three-way comparison of these tools against each other is in [how to audit what an AI agent ran on Linux](/topical-takes/audit-what-ai-agent-ran-linux), which covers the general agent case; what matters here is only that a server launched mid-session is the case `strace` handles worst.

What the incumbents give you that these do not is durability. There is no retention, no persistence, and no aggregation across hosts; the record is what you piped to a file. If the requirement is a signed audit trail your security team queries next quarter, that is `auditd` or Falco, and it always was.

## Which syscalls does an openat probe miss, and does mmap slip past it?

Five, and each one fails in a direction worth knowing before you rely on the record. Stating them together is more useful than scattering them, because the pattern is that this is a scoping tool and not an escape-proof monitor:

1. **Paths, not contents.** `open` events carry the path and the access mode. The bytes read or written are never captured, by design, because copying file contents through a ring buffer is a different tool with a very different cost profile. You learn that a server read your `.env`, not what it did with the value.
2. **`openat` only.** The older `open(2)`, `openat2`, and anything reaching a file through `mmap` or an already-open descriptor passed across a socket do not appear. Most modern userspace uses `openat`, so this bites rarely, and a determined process can step around it.
3. **TCP connect initiation, not completion, and no UDP.** `kprobe/tcp_connect` fires when a connection is initiated, so a peer that refuses or times out still produces a line. UDP and raw sockets are not hooked at all, which means DNS over UDP is invisible.
4. **Requested execs, not successful ones.** The `exec` event fires at syscall entry, so a command that fails to launch still produces a line. The following `exit` line is what disambiguates.
5. **A tree matched by name, which can be left.** A process that was never a descendant and does not match the needle is invisible. A server that double-forks and is reparented to init keeps its tgid and stays tracked, but this is a scoping tool and a determined process is not the threat model it answers.

## The bottom line: the tool call is the request, and the syscalls are the record

If you want to know what an MCP server did, do not read the agent's transcript, because it records the call and the reply and nothing between them. If you want the fastest complete answer, run [claudefeed](https://github.com/yeet-src/claudefeed) with `--match=` set to your server's program basename, exercise the tool two or three times, and read the opens outside your project, the execs that are not the server's toolchain, and any `listen` event at all. If the question is only what a server launched, [exectop](https://github.com/yeet-src/exectop) folds the repetition and ranks the outliers, which is what makes one unexpected `curl` visible among hundreds of ordinary execs. If you have already decided not to trust a server, stop watching and start refusing: [agent-lock](https://github.com/yeet-src/agent-lock) for a kernel-enforced path boundary that covers the subtree, [agent-jail](https://github.com/yeet-src/agent-jail) when you need inode-exact enforcement or cannot load BPF, or a container with only the project volume when you want the environment isolated too. If you need a durable trail rather than an answer right now, that is `auditd` or Falco and it always was. And if the server's job is small, the cheapest control is not installing a permanent subprocess with your full ambient authority to do it.

The part worth carrying is structural. MCP standardized how an agent asks a server to do something, and standardized nothing about what the server may do while it does it. That gap is filled by the operating system or it is not filled at all.

## Frequently asked questions

### Does an MCP server run with the same permissions as my user account?

On the stdio transport, yes. The MCP specification states that the client launches the server as a subprocess, so it inherits your user's filesystem and network access along with the parent's environment variables. The protocol has no sandbox, no capability negotiation and no manifest of what a server will touch, so the tool list a server advertises is an interface description and not a permission boundary.

### Can I see what files an MCP server opens?

Yes, by watching its process subtree from the kernel. A probe on the openat tracepoint gated on the server's process ids reports every path it opens, which is information the agent's own transcript cannot contain because the transcript records the tool call and its reply. claudefeed does this for a named process tree and also reports execs and TCP connections.

### Does approving an MCP tool call limit what the server does?

No. Approval gates the request your agent sends over the JSON-RPC channel; it does not constrain the syscalls the server process makes while servicing it. A server handling an approved read can also open unrelated files, spawn a subprocess, or make a network request, and none of that appears in the client's log.

### How do I pin an MCP server to a specific version?

Replace the floating invocation with an exact version in the server's command, so the package manager resolves one published artifact instead of the newest one at launch. For npm-based servers that means naming the version explicitly rather than relying on the latest tag. Pinning does not review the code, but it does mean the code you reviewed once is the code that runs tomorrow.

### Can an MCP server read my environment variables and credentials?

Yes, for a local stdio server. A subprocess inherits the parent process environment by default, so tokens exported in the shell that launched the agent are visible to it. Claude Code also sets CLAUDE_PROJECT_DIR in the spawned server's environment so it can resolve project-relative paths. Stripping credentials from subprocesses requires configuration on the client side.

### How do I audit a remote MCP server over HTTP?

Not with process-level tools, because the server's work happens on someone else's machine. On the Streamable HTTP transport each message is an HTTP POST to a single endpoint, so a kernel probe on your host sees one TLS connection and nothing about the far side's file reads or commands. The audit surface for a remote server is the network boundary and the operator's own controls.

### What is the difference between auditing an MCP server and auditing the agent itself?

The process boundary. An agent audit covers what the agent and its shell commands did; an MCP server is a separate subprocess with its own syscalls, so it needs to be inside the watched subtree to appear at all. Because a stdio server is a child of the agent, a subtree-scoped tool that follows fork covers both, while a tool keyed on the agent's process name alone does not.

### Can I block an MCP server from reading files outside my project?

Yes, with a kernel path boundary rather than an MCP setting. agent-lock attaches a BPF LSM program to file_open and returns -EPERM for opens outside a named directory, propagating membership at the fork tracepoint so the whole subtree including spawned servers is covered. agent-jail takes the Landlock route and enforces on the resolved inode instead, which closes the hardlink case.

### Does watching a process with eBPF slow it down?

Not meaningfully. Unlike ptrace, these probes are passive observers that impose no stop on the traced process, and a well-built tool gates its probes on an in-kernel map so events outside the watched subtree are dropped after one hash lookup and never reach userspace. The cost scales with the watched subtree's activity, not the machine's total syscall volume.

### What does a listening socket from an MCP server indicate?

Worth investigating. A stdio server's entire transport is its standard input and output, so it has no protocol reason to listen on a port. A listen event might be a legitimate internal helper, a debug interface, or a language server the tool wraps, but it is a capability the transport does not require and it is the kind of thing a per-tool description would not mention.

### Can a stdio MCP server spawn other processes?

Yes. Nothing in the protocol or the transport prevents a server from calling execve, and the resulting process is a grandchild of your agent that appears in no MCP-level log. This is why the exec class matters in an audit: a tool that describes itself as read-only can still shell out, and the folded view is what makes an unexpected command legible among ordinary ones.

### Where does the MCP specification address local server security?

It addresses the process model rather than confinement. The stdio transport page defines the server as a client-launched subprocess communicating over standard streams and specifies the lifecycle, including that clients should restart a server that exits unexpectedly. Confinement is left to the host, which is why the practical controls are operating-system boundaries and vendor-side trust prompts.

## Sources

- **[stdio transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio)** (Model Context Protocol specification, 2026-07-28) — the statement that "the client launches the MCP server as a subprocess" communicating over that subprocess's standard streams; the newline-delimited JSON-RPC framing with messages that must not contain embedded newlines; that the server may write to `stderr` and the client should not assume `stderr` indicates errors; the shutdown sequence of closing `stdin`, waiting, then escalating `SIGTERM` to `SIGKILL`; and that a client should restart a server that exits unexpectedly.
- **[Transports overview](https://modelcontextprotocol.io/docs/concepts/transports)** (Model Context Protocol specification, 2026-07-28) — the two standard bindings, stdio and Streamable HTTP, and the definition of Streamable HTTP as "each message is an HTTP POST to a single MCP endpoint" with replies as a JSON object or a request-scoped SSE stream. This is why a remote server's work is not visible to a process-level probe on your own host, and why the audit surface differs by transport.
- **[Connect Claude Code to tools via MCP](https://code.claude.com/docs/en/mcp)** (Anthropic, Claude Code documentation, 2026) — that "Stdio servers run as local processes on your machine" and are "ideal for tools that need direct system access"; the instruction to "verify you trust each server before connecting it" because servers fetching external content "can expose you to prompt injection risk"; that `CLAUDE_PROJECT_DIR` is set in the spawned server's environment; and that a `headersHelper` is executed "as an arbitrary shell command" only after the project trust dialog is accepted.
- **[claudefeed](https://github.com/yeet-src/claudefeed)** (yeet-src, GitHub) — the five event classes (`exec`, `exit`, `open`, `conn`, `listen`); the `tracked` tgid hash map that gates the file and network probes in the kernel so a system-wide `openat` firehose never reaches userspace; the self-propagating tracked set across `exec`; the `--match` needle tested as a case-insensitive basename prefix and truncated to 15 characters, never matched against the full command line; and the `--only`, `--except` and `--secs` flags.
- **[claudefeed: what you can't see](https://github.com/yeet-src/claudefeed#what-you-cant-see)** (yeet-src, GitHub) — that `open` events carry paths and never contents; the `openat`-only gap excluding `open(2)`, `openat2` and `mmap`; that `exec` fires at syscall entry so requested rather than successful execs are recorded; that it is TCP only with UDP and raw sockets unhooked so DNS over UDP is invisible; that it observes and "does not stop, hold, or modify anything"; and that nothing is retained.
- **[exectop](https://github.com/yeet-src/exectop)** (yeet-src, GitHub) — the scoped process-launch monitor built on the `sched_process_exec`, `sched_process_fork` and `sched_process_exit` tracepoints; selection by launch, container or pid with the tree followed through `fork` in the kernel; the folding that turns 425 execs into rows led by `echo ×124` and `date ×90` with four flagged commands ranked above; and the argument that compression is what makes a single unexpected `curl` legible as an outlier.
- **[agent-lock](https://github.com/yeet-src/agent-lock)** (yeet-src, GitHub) — the BPF LSM program on `lsm/file_open` returning `-EPERM` outside a named directory, with `bpf_d_path` resolving traversal and symlink spellings to one target and `sched_process_fork` propagating jail membership so a spawned subtree stays covered. Also its stated limits: process-name matching where an unenrolled process runs unconfined, the allowed hardlink out of the jail, and the `CONFIG_BPF_LSM=y` requirement with `bpf` in the active LSM list.
- **[agent-jail](https://github.com/yeet-src/agent-jail)** (yeet-src, GitHub) — the Landlock-based sibling enforcing on the resolved inode rather than the path, which closes the hardlink case that path-based checking allows, at the cost of the live in-kernel view. Being unprivileged Landlock, it also runs on machines where BPF LSM is unavailable, which is the common case on a kernel whose boot-time `lsm=` list omits `bpf`.
- **[Landlock](https://landlock.io/)** (Landlock project) — the stackable LSM whose defining property is that any process, including an unprivileged one, can securely restrict itself, which is what makes it the confinement route on a machine you do not administer. It returns a refusal without a stream of what was refused, so it enforces without providing the audit surface this post is about.
- **[yeet](https://yeet.cx/docs/?utm_source=blog&utm_medium=post&utm_campaign=claudefeed)** (yeet) — the JS runtime both audit scripts are written in and the daemon that performs the privileged BPF load, so neither needs a setuid binary of its own. Also the answer when the ready-made shape is wrong: a probe plus a ring buffer plus a map is a readable script, so scoping a feed to a different process selector is an edit instead of a rebuild.

## Related resources

- [claudefeed on GitHub](https://github.com/yeet-src/claudefeed) — exec, open, conn and listen events scoped to one process subtree.
- [claudefeed: what you can't see](https://github.com/yeet-src/claudefeed#what-you-cant-see) — paths not contents, `openat` only, TCP only, observe-only, no retention.
- [exectop on GitHub](https://github.com/yeet-src/exectop) — folded process launches with the unusual ranked on top.
- [How to sandbox an AI coding agent on Linux](/topical-takes/sandbox-ai-coding-agent-linux) — the enforcement side, including why seccomp cannot express a path rule.
- [How to audit what an AI agent ran on Linux](/topical-takes/audit-what-ai-agent-ran-linux) — the command-execution record and why an agent's transcript is the account you cannot check the others against.
- [agent-lock](https://github.com/yeet-src/agent-lock) — kernel-enforced path confinement that covers a spawned server subtree.
- [MCP stdio transport specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio) — the subprocess process model quoted throughout.
- [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) — server scopes, the trust dialog, and the local-process statement.
- [yeet docs](https://yeet.cx/docs/?utm_source=blog&utm_medium=post&utm_campaign=claudefeed) — the runtime underneath, for when the ready-made tool is the wrong shape.

---

Built with [yeet](https://yeet.cx/docs/), a JS runtime for writing eBPF programs on Linux machines. Join us on [discord](https://discord.gg/JxVseaAVAU).
