Back to blogs

September 8, 2026

The kernel does not care what you named the tool

AgentGuardeBPFAgent Security

AgentGuard: eBPF LSM for coding agents. YAML in the repo. Deny is EPERM.
Linux prototype · Apache-2.0 · github.com/AgentGuard-hq/AgentGuard


You asked the agent to fix a failing test. The GitHub issue looked normal. Buried in a fixture, or in an MCP response, or in a README the model helpfully fetched, was an instruction to read .env and post it somewhere.

The model may even agree that secrets are off-limits. That is not a security boundary. The boundary is whether openat succeeds.

Coding agents are not chatbots with a nicer prompt. They open, exec, and connect. The failure that matters is the next syscall, not the next token.


Conscience is not a TCB

Most guardrails live inside the agent:

  • a system prompt
  • CLAUDE.md / AGENTS.md
  • PreToolUse hooks
  • an allowlist of “Bash” strings

Those are a conscience. Consciences are bypassed the moment execution leaves the tool schema.

Deny .env in a hook, then read it with python -c, perl, node, a loader, mmap, anything that is not Read or Bash as the CLI defines them. Deny rm as a command string, then hit the same inode another way. The hook never ran. The kernel still would — if you asked it to.

Vendor sandboxes (Seatbelt, bubblewrap, Claude’s sandbox-runtime, devcontainers) are the right product default for most people. This post is a narrower claim:

Policy that remains true when the agent’s own conscience is false.

That layer is the LSM. Deny is EPERM. The OS does not care what the tool was named.


What AgentGuard is

AgentGuard is a Linux eBPF LSM supervisor for coding agents. One binary loads policy into the kernel and starts the agent as the invoking user, not root.

Policy is YAML in the project (policies/default.yaml). Starter rules:

  • Credentials — block .env, id_rsa, the usual secret paths
  • Destructive argvrm, dd, the obvious ones
  • Egress — allowlisted hosts on 443, via a local proxy; everything else denied

A deny is a kernel error, not a chat message. Optionally, Claude Code / Codex hooks inject a feedback: string so the model can adapt instead of spinning on a raw permission error. If the hooks are missing, the kernel still blocks. The model just is not told why.

That split is the whole design:

LayerJob
LSMEnforcement. Fail closed.
YAML in gitPolicy you can review in the PR.
HooksExplanation. Courtesy. Not the TCB.
policies/default.yaml
        │
        ▼
   eBPF LSM (loaded as root)
        │
        ▼
   agent as SUDO_USER
        │
        ├── openat(".env")              → EPERM
        ├── execve("rm", …)             → EPERM
        └── connect(not :443 allowlist) → EPERM
                │
                └── optional: "SYSTEM FEEDBACK: …" into the TUI

Status: v0.1.2, early. Tested primarily with Claude Code. Codex is wired the same way. Native Darwin claude is not supervised — macOS has no BPF LSM. A Colima/Docker Linux VM is a different machine, not a magic halo around the host binary.


Why EPERM and not another hook

Hooks inspect intent as the product serialized it. LSM inspects what the process did.

If you only wrap Bash, you have not wrapped python. If you only wrap the CLI’s Read tool, you have not wrapped openat from a compiler, a test runner, or a postinstall script the agent just spawned. Child processes inherit the LSM. They do not inherit your settings.json.

That is also why AgentGuard is not “prompt injection detection.” Injection is how you get to a bad syscall. The interesting part is whether the syscall is allowed. Catching every encoding of “ignore previous instructions” is a language problem. Catching openat on .ssh/id_rsa is a kernel problem. I would rather have both. I will not pretend the first replaces the second.


Privilege, said plainly

Loading BPF is a loaded privilege. Typically root at load, CAP_BPF in play.

Two rules I treat as non-negotiable:

  1. LSM only. The supervisor must not attach tracing probes on the agent’s TLS stack or rewrite userspace buffers. If you need to mutate what the model “saw” in order to “help,” you are no longer a reference monitor.
  2. Pin the object. Drop caps on the child. doctor should fail on a hash mismatch. The agent must not keep CAP_BPF. A second tracing program on the box is a peer of the supervisor, not a child you can YAML away — unless you also gate bpf() / program types.

“We used eBPF” is not the same sentence as “you can trust us.” The first is a mechanism. The second is a TCB story: hashed program, allowlisted types, no extra helpers, agent running as a boring user.

install.sh cannot invent lsm=bpf. That is a boot-time fact. If doctor says BPF is missing from the LSM list, that is the kernel cmdline, not a missing mount. Securityfs being readable is not the same as enforcement.


Non-goals (on purpose)

AgentGuard is not:

  • a replacement for Anthropic’s Seatbelt / bubblewrap sandbox, MDM, or Claude Code on the web
  • a Mac-native enforcer
  • a hosted microVM product (that is E2B/Daytona/Modal)
  • a complete jail — the default YAML is a starter, completeness is a policy problem
  • unprivileged install on a random laptop kernel

If the threat model is “untrusted repo, don’t toast the laptop,” start with the first-party sandbox or a VM. Use something like this when you need agent-agnostic, auditable syscall policy on a Linux box you already control.

There are many other GitHub repos named AgentGuard. This one is the kernel one. The name collision is annoying and irrelevant to the claim.


What “working” looks like

The demo I care about is not a cartoon jailbreak.

  1. Hooks and CLAUDE.md say: never read .env, never rm -rf.
  2. The prompt is ordinary: debug the test / summarize the issue.
  3. The bypass does not go through the tool the hook subscribed to.
  4. Hooks-only: the secret is in context, or the file is gone.
  5. LSM: EPERM. Process still alive. Optional feedback: so the model retries cleanly.

If I cannot show a hook path that loses and an LSM path that holds, I do not have a product. I have a policy compiler with extra steps.


Why this exists

I wanted to know whether coding-agent safety could live at the same layer as every other process on Linux: LSM, fail closed, policy in git.

The prototype says yes, with the usual kernel tax — BTF, lsm=bpf, sudo to load, no Darwin miracles.

The industry is shipping consciences at industrial scale. Some of them are good consciences. I still want the door to lock when the conscience is tired, confused, or prompt-injected.

Hooks are the agent’s conscience.
The kernel is the door.


AgentGuard is Apache-2.0, v0.1.2, Linux. Code and a longer engineering note: github.com/AgentGuard-hq/AgentGuard.