Governance Coverage
This page states what MITRITY governs and what it does not. Read it before you rely on MITRITY to govern an agent.
The page names each execution path, says whether the path crosses a MITRITY decision point today, and shows how to close the paths that do not. The same rule applies here as on the mesh egress allowlist and the SaaS connectors: MITRITY reports an action as enforced only when it decided it, and as seen only when it reached a MITRITY component.
The short version
MITRITY governs the actions that cross a MITRITY decision point. Every decision point that exists today is listed below.
Anything an agent does without crossing one of them is not evaluated, not blocked and not in your audit log. In practice, that means two things, and they are the two things that surprise most people:
- Your agent framework's own built-in execution tools never produce an MCP
tools/call. Examples are Claude Code'sBash, LangChain'sShellTooland CrewAI's code interpreter. Pointing an agent at Mitrity Gateway governs the tools the gateway serves. It does not govern the tools the framework already had. - Code the agent runs itself never reaches a MITRITY binary at all. Examples are a
subprocess.run(...)inside a custom tool, or a command a human types in the same shell.
You can close both with configuration. Make the gateway the only execution path is the checklist. For the first, mitrity-hook and the language adapters can also put a framework's built-in tools in front of the edge: see Framework built-in execution tools.
Governed today
| Decision point | What is evaluated | Where |
|---|---|---|
Mitrity Gateway: MCP tools/call | Every tool call to a gateway-aggregated upstream, before dispatch | Integration Modes |
Mitrity MCP Sidecar: MCP tools/call | Every tool call to the one wrapped upstream server, before forwarding | Integration Modes |
| Native HTTP tools on the gateway | HTTP APIs declared as tools in native_tools[]. The gateway issues the request, so the same pipeline applies | Configuration Reference |
Mitrity Mesh Authorizer (mitrity-extauthz) | East-west and agent-to-agent (A2A) requests in Istio, plus mesh egress by destination/SNI | Mesh Enforcement |
Mitrity LLM Gateway (mitrity-llm-gateway) | Prompts and model responses on LLM egress: model allowlists, token caps, budgets, rate limits, wire redaction | LLM Gateway |
| Admission API on the gateway and the sidecar | Framework built-in tool calls, such as Claude Code's Bash, before they run. mitrity-hook and the language adapters ask it | Admission API |
| Platform connectors (SaaS-embedded agents) | Discover, Observe, Posture: read-only, after the fact | SaaS-Embedded Agents |
On the runtime surfaces, the full pipeline runs before the action executes. The pipeline covers:
- policy and rule constraints;
- DLP;
- prompt-injection scanning;
- threat-intel matching;
- delegation-chain state;
- mission-scoped credential injection;
- human-approval holds.
Platform connectors are the exception. They observe and assess, but they do not block.
What "MCP" covers, precisely
On MCP, only tools/call is governed.
- Mitrity MCP Sidecar intercepts
tools/calland forwards every other JSON-RPC method to the upstream untouched, includingresources/read,prompts/getandsampling/*. - Mitrity Gateway implements
initialize,tools/list,tools/callandping. Any other method returns a JSON-RPC Method not found error instead of being governed.
If an upstream MCP server exposes sensitive data as a resource rather than a tool, the sidecar passes that read through and the gateway refuses it. Neither one is governance.
Not governed today
Each entry below describes the path, and how you can close or narrow it.
Framework built-in execution tools
Every major agent framework ships tools that execute code or touch the filesystem without going through MCP: shells, code interpreters, file writers and web fetchers. They run inside the agent process and never produce a tools/call. Without a hook or an adapter, no MITRITY binary sees them.
You close this path in one of two ways. The per-framework settings and tools are in the table below.
- Disable those tools.
- Put them in front of the edge.
mitrity-hookdoes this for Claude Code and the Claude Agent SDK, and the language adapters do it for the Claude Agent SDK, LangChain, the OpenAI Agents SDK and CrewAI. Each built-in call is then decided through the admission API before it runs.
Direct OS calls from the agent's own code
A custom tool that calls subprocess (Python), os/exec (Go) or any other process-spawning API executes directly. So does a command that a person types on the same host. Neither one crosses a decision point, so neither is evaluated or recorded.
For that code, containment is structural, not policy-driven. Run the agent:
- in a distroless container with no shell and no package manager;
- as a non-root user;
- with credentials held by the gateway rather than the agent.
Two components narrow this path:
- The governed shell gives the agent a governed way to run commands: the gateway executes exactly the bytes it judged, inside a kernel sandbox. Code that starts processes itself does not use it.
- On Linux, the host observer attributes the rest of the agent's process executions. That is detection, not prevention.
Wrapped and scripted commands
Two evasions are caught on the governed paths, with the caveats stated below.
Wrappers. The command line is parsed with a real shell grammar. bash -c "rm -rf /", sudo, env, timeout, xargs, find -exec, pipelines and inline interpreters are unwrapped. Rules match the resolved commands (resolved_commands, exec_features, on_unparseable) rather than the first word. What cannot be resolved, such as a decoded payload piped into a shell or a dynamic command word, is a named feature that fails closed.
Scripts. When a command references a script, the script is read under the policy's script_inspection_roots. For shell scripts, its inner commands are merged into the evaluated tree. For example, a deny rm rule fires on bash deploy.sh when the script contains rm -rf /.
An agent can also write the commands in one call and run the file in another. The edge taint store links the two calls:
script_authoringon the write;write_then_runon the execution;modified_outside_governancewhen the file changed in between.
taint_action / autorun_write_action decide what happens. See Scripts, write-then-run and autorun paths.
What this does not cover:
-
Non-shell script bodies. A Python, Node, Perl or Ruby script is hashed, DLP-scanned and injection-scanned, but not semantically analyzed. The sandbox bounds its effect; the parser does not understand it.
-
Code handed to a tool the edge does not treat as executing. A
codeargument is content. It is fingerprinted, DLP- and injection-scanned and, on a write, judged as the script being authored. But it is parsed as a command only on a tool whose name says it executes it (execute_code,python_repl).On every other tool, a
codebody that no rule judged carriescode_body_unjudged. That covers a path-less call, or a written body that is not a shell program. The finding names the tool and the key, so the gap is visible per tool in the audit log rather than silent. A shell body written through such a tool is judged as the script being authored and needs no stamp.Two policy settings govern the gap:
code_execution_toolsnames further tools (bytool_pattern) whosecodeis judged as the command they run;unjudged_code_actiondecides what happens to an action that still carries the finding: alert by default, hold or deny (Writing Policies).
-
TOCTOU outside the governed shell. TOCTOU (time of check to time of use) is the window between the decision and the execution. The script's hash is recorded at decision time. The framework or upstream tool runs the file afterwards, and the file can change in between. That change is detected on the next decision (
modified_outside_governance), not prevented. Only the first-party governed shell closes the window, because it executes the exact bytes it judged inside a kernel sandbox. Its page says exactly what it does and does not bound. -
Scripts outside the roots are not read at all. The execution is
script_unresolvableand denied by default. This is a configuration gap that you widen deliberately, never a silent pass. -
Host activity outside governed paths. On macOS and Windows, a script that the agent runs without crossing a decision point is invisible. See below.
MCP resources, prompts and sampling
As described above, the sidecar forwards them, the gateway refuses them, and neither one governs them.
DNS and raw sockets
Governed egress is HTTP(S)-shaped. The mesh egress allowlist works at the network layer and is based on destination/SNI. The LLM Gateway terminates TLS for LLM traffic only. An agent that resolves names or opens raw sockets directly is outside both.
Lock this path down with network controls you already own: a NAT egress allowlist, or Istio REGISTRY_ONLY plus the mesh egress allowlist.
Vendor-cloud hosted tools
The OpenAI Agents SDK's CodeInterpreterTool, HostedMCPTool and WebSearchTool execute in OpenAI's cloud, not on your host. No local edge can see or stop them. This is a structural limit, not a missing feature. If a workload must be governed, do not give it hosted tools.
Nothing local can change this. The OpenAI Agents SDK adapter attests hosted tools as unhooked, so the agent's coverage posture shows the gap rather than assuming it away.
Host activity on macOS, Windows and serverless
Runtime observation of what an agent's process tree actually executed is a Linux capability, because it reads kernel process events. There is no equivalent on macOS, on Windows, or in serverless runtimes such as Cloud Run and Lambda, where no kernel-level process view is available to a workload.
On Linux, mitrity-host-observer observes it. macOS, Windows and serverless host activity are not observed.
Agent frameworks: built-in execution tools
The settings in the third column need nothing from MITRITY. The fourth column is what MITRITY provides on top of them.
| Framework | Built-in execution tools | How to disable or restrict them today | What MITRITY adds |
|---|---|---|---|
| Claude Code / Claude Agent SDK | Bash, Write, Edit, MultiEdit, NotebookEdit, WebFetch, WebSearch. All of them bypass MCP | permissions.deny with Bash(...) rules; the native Bash sandbox: sandbox.enabled: true, allowUnsandboxedCommands: false, failIfUnavailable: true, sandbox.network.allowedDomains; restrict MCP to the gateway with allowedMcpServers; make hooks non-removable with allowManagedHooksOnly | mitrity-hook: a PreToolUse hook that decides every built-in tool call before it runs (deny via exit code 2 / permissionDecision: "deny"), and a renderer that generates the managed settings above from the agent's mission profile. The Claude Agent SDK adapters for Python and TypeScript decide the same calls in-process |
| LangChain / LangGraph | ShellTool, PythonREPLTool, FileManagementToolkit | Do not put them in the tools list you pass to the agent. Expose only the gateway's MCP tools. There is no runtime switch: the tool list is the control | The Python adapter wraps a LangChain tool (govern(tool)), so the edge decides each invocation before it runs |
| OpenAI Agents SDK | LocalShellTool (local); CodeInterpreterTool, HostedMCPTool, WebSearchTool (hosted, run in OpenAI's cloud) | Omit LocalShellTool. The hosted tools cannot be governed by any local edge, so omit them from agents that must be governed | The Python adapter (govern(agent)) decides function tools and the local shell and patch tools before they run. Hosted tools pass through and are attested as unhooked, so the coverage posture shows them |
| CrewAI | CodeInterpreterTool; allow_code_execution=True on an Agent | Leave allow_code_execution at its default (False) and do not add CodeInterpreterTool | The Python adapter wraps a CrewAI tool (govern(tool)). The tools CrewAI adds itself, such as the code interpreter behind allow_code_execution, are not wrapped |
| smolagents | PythonInterpreterTool; a CodeAgent executes the Python it generates, by design | Use ToolCallingAgent instead of CodeAgent, and do not add PythonInterpreterTool. If you need CodeAgent, treat the agent as ungoverned for execution | No adapter. On Linux, the host observer attributes what the agent actually executed, after the fact |
| Custom code (any language) | Whatever the agent process can call: subprocess (Python), os/exec (Go), and equivalents | Nothing in a framework to switch off. Route execution through a governed tool, and contain the process: distroless image, non-root, no shell, no package manager, no credentials in the agent | The governed shell (the shell__execute tool, action type shell:execute), a first-party gateway tool that gives the agent a governed execution path to route to; the Go adapter, which wraps a Go agent's tool functions |
Make the gateway the only execution path
Governance coverage is a property of your configuration, not of the binary. This checklist produces it:
- Expose exactly one MCP server to the agent: the gateway. Remove every other
mcpServersentry. On Claude Code, enforce this withallowedMcpServers. - Turn off the framework's built-in execution tools, using the settings in the table above, or put them in front of the edge with
mitrity-hookor a language adapter. This is the step people skip most often, and it is the one that matters most. - Give the agent process no credentials. Upstream secrets belong to the gateway, and the agent authenticates to the gateway. An agent that holds a token can use it without asking.
- Run the agent in a container with no shell and no package manager, as a non-root user, from a distroless base. If there is no
sh, a great deal of improvised execution simply fails. - Lock egress. Use a NAT or firewall allowlist, or Istio
REGISTRY_ONLYplus the mesh egress allowlist, so the agent cannot reach anything the gateway did not route. - Spawn the edge from the agent's own MCP client over stdio. Both binaries are stdio programs. A detached "gateway service" that the agent dials over the network is not a supported topology. See the deployment guide.
- Verify against the audit log, not against intent. Run the agent through a realistic task, and compare what it did with what the audit log contains. Anything it did that is not in the log was, by definition, ungoverned.
The execution-coverage badge
Every agent in the dashboard carries an execution-coverage badge, and the agent page has an Execution coverage card behind it. The badge answers one question: is this agent actually governed? It answers from two independent sources, never from one:
- Attested: what the runtime says about itself. On Claude Code and the Claude Agent SDK,
mitrity-hook session-startreports which built-in tools the hook intercepts, which execution-capable tools it does not, which other MCP servers are configured, the permission mode and the sandbox settings. This is self-reported by the process the model runs inside. It is evidence, not proof: a hook that was quietly removed still leaves a settings file that says it is there. - Observed: what the edge actually saw. On every heartbeat, the gateway reports how many admission decisions and MCP calls it handled in the last minute. The control plane keeps the last time each was ever seen. The runtime does not control these counters.
The badge is the intersection of the two. A self-asserted attestation alone never turns it green.
The five states
| Badge | Meaning | What turns it into governed |
|---|---|---|
| Governed | The runtime attests full coverage, nothing is flagged, and the edge has seen the hook in the loop (at least one admission decision). | — |
| Partial | Attested, but either not yet corroborated by traffic, or not fully isolated, because a medium or low finding is open. On a fresh install, the most common reason is simply that no built-in tool call has passed through the hook yet. | The first admission decision, or closing the open finding. |
| Degraded | Part of the execution surface is outside governance, because a high finding is open: an execution tool the hook does not cover, a second MCP server, or a sandbox that allows commands to escape it. A notification (agent.execution_coverage_degraded) fires when an agent enters this state. | Closing every high finding. |
| Unattested | The runtime's framework has built-in execution tools (Claude Code's Bash, LangChain's ShellTool, …) and it has never attested. The hook or adapter is not installed, or never reached the edge. | Installing the hook and starting a new session. |
| Unknown | Nothing to assess against: there is no attestation, and either no framework is named or the named framework has no catalogued built-in execution tools. This is not a pass and not a failure. MCP calls through the gateway are governed either way; execution from the agent's own code is not observable on this runtime. | An adapter for the framework, when one exists. |
Why attestation alone never means governed
The attestation comes from inside the agent's trust boundary. That is the same process the model runs in, along with anything that has persuaded the model. The attestation can be accurate but stale, accurate but misconfigured, or simply gone while its settings file remains. So the control plane treats it as one axis and demands corroboration from the other:
- An attestation that hooks tools but has never produced an admission decision reads partial (
no_corroborating_traffic), not governed. - An attestation that hooks tools while ten consecutive heartbeats carry MCP calls and zero admission decisions reads partial (
hook_silent). The settings file names a hook that the edge is not hearing from. - An attestation that stops being carried by heartbeats for 15 minutes reads partial (
attestation_stale). Freshness is measured on the last heartbeat that carried it, because the hook attests once per session and the edge re-ships the attestation on every beat. - An edge that restarts without the runtime re-attesting reads unattested, not the last thing it said.
The Linux host observer is the third axis:
- An exec whose process ancestry never passed through a MITRITY binary counts against the badge, whatever the runtime attests (
observer_ungoverned_execution, high). - A host whose observer falls silent reads partial (
observer_offline, medium).
The card's observed axis shows whether the observer is present for the agent, its last event and the ungoverned count of the last 24 hours. Presence on its own never improves the badge.
Findings and how to close them
The card lists every open finding, with its severity, when it was first seen and what to change. These are the findings:
| Finding | Severity | What it means | Remediation |
|---|---|---|---|
attestation_missing | high | The framework has built-in execution tools and the runtime has never attested. | Install mitrity-hook and add its SessionStart and PreToolUse entries to the runtime settings (as managed settings on managed machines). Then start a new session. |
attestation_stale | medium | No heartbeat has carried the attestation for more than 15 minutes. | Check that the edge is running and heartbeating. If the runtime restarted, a new session re-attests on its own. |
unhooked_exec_tools | high | Execution-capable built-in tools that the hook does not intercept. Tools that the runtime disables outright are not counted. | Extend the PreToolUse matcher to cover the listed tools, or disable them with permissions.deny. On managed machines, set allowManagedHooksOnly so that the matcher cannot be narrowed locally. |
other_mcp_servers | high | MCP servers configured next to the MITRITY gateway. Each one is a second, ungoverned tool path. | Remove the other mcpServers entries, or pin allowedMcpServers to the gateway alone (allowManagedMcpServersOnly on managed machines). |
sandbox_disabled | medium | The framework has an OS sandbox, and it is disabled or not configured. | Set sandbox.enabled: true. |
unsandboxed_commands_allowed | high | The sandbox is on, but commands may run outside it. This includes the case where allowUnsandboxedCommands is simply absent, which is the framework default. | Set sandbox.allowUnsandboxedCommands: false. |
sandbox_not_required | low | The runtime runs unsandboxed, rather than refusing, when the sandbox cannot start. | Set sandbox.failIfUnavailable: true. |
permission_mode_bypass | medium | The session runs in bypassPermissions or dontAsk, so the framework's own prompts are skipped and the hook is the only gate. | Run the session in default or acceptEdits mode. |
hook_silent | medium | Hooked tools are attested, but ten consecutive heartbeats carried MCP calls and no admission decisions. | Run mitrity-hook doctor on the host. Confirm that the binary is on PATH, that the admission socket is reachable and that the PreToolUse command resolves. |
no_corroborating_traffic | medium | Attested, but no admission decision has ever been observed for this agent. | Nothing is wrong yet. The badge turns governed after the first built-in tool call passes through the hook. If the agent has been active for a while, treat it as hook_silent. |
config_drift | info | The runtime's configuration hash changed within the last 24 hours. Shown as a note: it never moves the badge, and it clears on its own. | Review the settings change. It is expected after a deliberate change, and worth a look otherwise. |
A disabled hook engine (disableAllHooks) does not get its own finding. The hook reports it as every execution tool being unhooked, which is exactly what it is.
Installing the hook
mitrity-hook ships with every edge release, for Linux, macOS and Windows (Getting the binaries and images). It asks the admission API of a gateway or sidecar on the same host. The Admission API page has its caller contract and its configuration. To install it:
- Put the
mitrity-hookbinary on the runtime host'sPATH. - In the Claude Code settings, add
mitrity-hook session-startunderhooks.SessionStartandmitrity-hook pre-tool-useunderhooks.PreToolUse(matching the execution-capable tools). On managed machines, add them as managed settings, withallowManagedHooksOnly. - Start a new session.
The badge moves from unattested to partial on the first heartbeat that carries the attestation. It moves to governed on the first built-in tool call that the hook decides.
Filtering the agents list
The agents list has a coverage filter (?execution_coverage=<state>). It uses the same rules as the badges, so a filtered list and the badges it shows always agree. Filter for degraded and unattested first. Those are the agents whose execution is, in part or in whole, invisible to MITRITY today.
Agents that have not been approved
Everything above answers is this agent's activity reaching a decision point? This section answers the other half: what does the decision point do when nobody has approved the agent yet?
When lifecycle governance is enabled for your tenant, every agent carries an approval state next to its runtime status. One tenant setting decides what that state means at the edge. There are three settings, and they differ in what happens to the agent's actions:
| Setting | What happens to an action from an agent that is not approved |
|---|---|
| Off | Nothing. The approval state is a label in the dashboard, and the edge does not read it. No review is opened either (see below). |
| Alert | The action runs. Every event it produces is tagged with the agent's approval state, so a single filter gives you the list of what is acting without approval. The platform raises one notification per agent, not one per action. |
| Enforce | The action is refused at the edge, before the tool call, the command or the model request happens. The audit event records it as blocked by lifecycle, with a reason naming the state the agent is in. |
An approved agent is unaffected under every setting: no tag, no refusal and nothing added to the decision.
Off also switches the review workflow off. New agents start approved without a review, and periodic reviews are skipped when they fall due. So the frameworks page reads the agent registry as partial for as long as the setting stays Off.
Four things to know before you move a tenant to Enforce:
- Agents that already exist are approved. Turning lifecycle governance on does not stop yesterday's traffic. Only agents created afterwards start out unapproved, and only when the setting is Alert or Enforce.
- Alert first. Start with Alert, and you get a list of what is acting without approval. Work through it, then move to Enforce, and the same states start refusing.
- A refused agent keeps reporting. The gate refuses actions, but it never silences heartbeats, events or the agent's mission profile. A reviewer needs that evidence to approve or reject the agent, so cutting it off would destroy the record of the thing being governed.
- Every entrance is covered. The gate sits inside the shared decision path. So it applies to MCP tool calls, framework built-in tools through the admission API, mesh traffic and LLM requests alike. There is no entrance that only checks policies and skips the approval state.
To the agent, a refusal looks like any other governed denial, with the same error shape a policy denial produces. The reason is in your audit trail, not in the agent's error message.
Execution states on events
Two closed vocabularies, each a fixed set of values, say on every event how much isolation and attribution actually applied. Neither one is a promise. Each value is stamped by the component that did the work, and a missing value means "not ours to vouch for", never "fine".
sandbox_mode — the governed shell
The governed shell stamps it on every execution-shaped event it produces (shell:execute, and a hooked Bash routed into it). Events from executions that the edge did not run carry no sandbox_mode at all: for example, a Bash that the framework executed itself, or an upstream MCP tool.
| Value | Meaning |
|---|---|
bubblewrap | Linux: bwrap with user, pid, net, ipc and uts namespaces, a read-only root and the seccomp stage installed. Full strength |
bubblewrap_no_seccomp | Linux: bwrap as above, without the seccomp stage. Only reachable with exec.sandbox.seccomp: preferred. Degraded; notifies |
seatbelt | macOS: sandbox-exec with the generated profile. Full strength for the platform |
none | The command ran with no kernel isolation, because the policy allowed it (sandbox_required: false). Degraded; notifies |
unavailable_denied | No sandbox backend was available, and sandbox_required: true denied the call. Nothing ran. Notifies |
A degraded mode raises execution.sandbox_degraded with the reason (userns_disabled, bwrap_missing, seccomp_unavailable, …). In a container, the usual reason is that the runtime's default seccomp profile blocks unprivileged user namespaces. The chart READMEs list the node prerequisites, and none of them is privileged.
classification — the host observer
mitrity-host-observer (Linux only) stamps it on every process execution it reports. It works from process ancestry alone and does not consult the runtime's own attestation.
| Value | Meaning |
|---|---|
governed | The execution passed through a MITRITY entrance: an ancestor is the agent's gateway or sidecar (or an attested upstream), the process is a MITRITY binary itself, or the control plane joined it to an admission decision |
ungoverned | The execution is inside an agent's process tree and did not pass through an entrance. This is the finding that counts against the coverage badge |
unattributed | Outside every known agent tree, or the ancestry chain could not be completed. Recorded only when a runtime rule matches. An incomplete chain is never reported as ungoverned, because the unseen link might have been the entrance |
Windows
Windows coverage is narrower than Linux and macOS:
| Windows today | |
|---|---|
| Mitrity Gateway | No Windows binary. The gateway ships as a Linux and macOS binary (amd64 and arm64), and as a Linux container image |
| Mitrity MCP Sidecar | Supported: windows/amd64 binary |
| Claude Code Bash sandbox | No native Windows implementation (Seatbelt on macOS, bubblewrap on Linux and WSL2). Under WSL2, the Linux sandbox applies |
| Host observation | Not available. The observer reads Linux kernel process events |
mitrity-hook | Supported: windows/amd64 binary, which asks the sidecar's admission API over loopback TCP. Command analysis assumes POSIX shell grammar |
For a Windows agent host, the practical combination is the sidecar, plus mitrity-hook or disabled built-in execution tools, plus network egress controls. The alternative is to run the agent under WSL2 and treat it as a Linux host.
Related Documentation
- Integration Modes: what the gateway and the sidecar each govern
- Deployment Guide: install both binaries, and the supported topologies
- Writing Policies: the constraint vocabulary that exists today, and its limits
- Governed Shell: the first-party sandboxed shell that executes exactly the bytes it judged, and what it does and does not bound
- Host Observer: the Linux observer that attributes every process execution to governance from kernel process ancestry, and the Runtime page
- Mesh Egress Allowlist: network-layer egress governance and its boundaries
- SaaS-Embedded Agent Governance: read-only coverage for agents in a vendor's cloud
- Enforcement Modes: monitor, alert, enforce