New: Agent approvals, red teaming and model scanning. See how
MITRITY
Docs / Gateway, Sidecar & Mesh / Governance Coverage

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:

  1. Your agent framework's own built-in execution tools never produce an MCP tools/call. Examples are Claude Code's Bash, LangChain's ShellTool and 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.
  2. 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 pointWhat is evaluatedWhere
Mitrity Gateway: MCP tools/callEvery tool call to a gateway-aggregated upstream, before dispatchIntegration Modes
Mitrity MCP Sidecar: MCP tools/callEvery tool call to the one wrapped upstream server, before forwardingIntegration Modes
Native HTTP tools on the gatewayHTTP APIs declared as tools in native_tools[]. The gateway issues the request, so the same pipeline appliesConfiguration Reference
Mitrity Mesh Authorizer (mitrity-extauthz)East-west and agent-to-agent (A2A) requests in Istio, plus mesh egress by destination/SNIMesh Enforcement
Mitrity LLM Gateway (mitrity-llm-gateway)Prompts and model responses on LLM egress: model allowlists, token caps, budgets, rate limits, wire redactionLLM Gateway
Admission API on the gateway and the sidecarFramework built-in tool calls, such as Claude Code's Bash, before they run. mitrity-hook and the language adapters ask itAdmission API
Platform connectors (SaaS-embedded agents)Discover, Observe, Posture: read-only, after the factSaaS-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/call and forwards every other JSON-RPC method to the upstream untouched, including resources/read, prompts/get and sampling/*.
  • Mitrity Gateway implements initialize, tools/list, tools/call and ping. 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-hook does 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_authoring on the write;
  • write_then_run on the execution;
  • modified_outside_governance when 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 code argument 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 code body that no rule judged carries code_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_tools names further tools (by tool_pattern) whose code is judged as the command they run;
    • unjudged_code_action decides 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_unresolvable and 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.

FrameworkBuilt-in execution toolsHow to disable or restrict them todayWhat MITRITY adds
Claude Code / Claude Agent SDKBash, Write, Edit, MultiEdit, NotebookEdit, WebFetch, WebSearch. All of them bypass MCPpermissions.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 allowManagedHooksOnlymitrity-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 / LangGraphShellTool, PythonREPLTool, FileManagementToolkitDo 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 controlThe Python adapter wraps a LangChain tool (govern(tool)), so the edge decides each invocation before it runs
OpenAI Agents SDKLocalShellTool (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 governedThe 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
CrewAICodeInterpreterTool; allow_code_execution=True on an AgentLeave allow_code_execution at its default (False) and do not add CodeInterpreterToolThe 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
smolagentsPythonInterpreterTool; a CodeAgent executes the Python it generates, by designUse ToolCallingAgent instead of CodeAgent, and do not add PythonInterpreterTool. If you need CodeAgent, treat the agent as ungoverned for executionNo 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 equivalentsNothing 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 agentThe 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:

  1. Expose exactly one MCP server to the agent: the gateway. Remove every other mcpServers entry. On Claude Code, enforce this with allowedMcpServers.
  2. 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-hook or a language adapter. This is the step people skip most often, and it is the one that matters most.
  3. 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.
  4. 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.
  5. Lock egress. Use a NAT or firewall allowlist, or Istio REGISTRY_ONLY plus the mesh egress allowlist, so the agent cannot reach anything the gateway did not route.
  6. 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.
  7. 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-start reports 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

BadgeMeaningWhat turns it into governed
GovernedThe runtime attests full coverage, nothing is flagged, and the edge has seen the hook in the loop (at least one admission decision).—
PartialAttested, 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.
DegradedPart 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.
UnattestedThe 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.
UnknownNothing 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:

FindingSeverityWhat it meansRemediation
attestation_missinghighThe 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_stalemediumNo 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_toolshighExecution-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_servershighMCP 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_disabledmediumThe framework has an OS sandbox, and it is disabled or not configured.Set sandbox.enabled: true.
unsandboxed_commands_allowedhighThe 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_requiredlowThe runtime runs unsandboxed, rather than refusing, when the sandbox cannot start.Set sandbox.failIfUnavailable: true.
permission_mode_bypassmediumThe 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_silentmediumHooked 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_trafficmediumAttested, 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_driftinfoThe 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:

  1. Put the mitrity-hook binary on the runtime host's PATH.
  2. In the Claude Code settings, add mitrity-hook session-start under hooks.SessionStart and mitrity-hook pre-tool-use under hooks.PreToolUse (matching the execution-capable tools). On managed machines, add them as managed settings, with allowManagedHooksOnly.
  3. 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:

SettingWhat happens to an action from an agent that is not approved
OffNothing. The approval state is a label in the dashboard, and the edge does not read it. No review is opened either (see below).
AlertThe 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.
EnforceThe 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.

ValueMeaning
bubblewrapLinux: bwrap with user, pid, net, ipc and uts namespaces, a read-only root and the seccomp stage installed. Full strength
bubblewrap_no_seccompLinux: bwrap as above, without the seccomp stage. Only reachable with exec.sandbox.seccomp: preferred. Degraded; notifies
seatbeltmacOS: sandbox-exec with the generated profile. Full strength for the platform
noneThe command ran with no kernel isolation, because the policy allowed it (sandbox_required: false). Degraded; notifies
unavailable_deniedNo 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.

ValueMeaning
governedThe 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
ungovernedThe 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
unattributedOutside 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 GatewayNo Windows binary. The gateway ships as a Linux and macOS binary (amd64 and arm64), and as a Linux container image
Mitrity MCP SidecarSupported: windows/amd64 binary
Claude Code Bash sandboxNo native Windows implementation (Seatbelt on macOS, bubblewrap on Linux and WSL2). Under WSL2, the Linux sandbox applies
Host observationNot available. The observer reads Linux kernel process events
mitrity-hookSupported: 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.