raxit
Troubleshooting

Known ungoverned paths and how to detect them

The calls raxit's autopatch does not intercept — what each one is, how to detect it, and how to close it.

A security product should be able to name its own blind spots. raxit governs tool calls by hooking the point inside each framework where tools are dispatched — but a call that never reaches that point is never seen, and a call routed around the interception on purpose is never seen either. This page collects every such path we know about, each already documented on its own framework or concept page, in one place. For every one: what it is, how to detect it, and how to close it.

The common thread: a governed call always leaves a decision record. If a tool ran and produced no entry in raxit audit tail, it was not governed — whatever the reason. That single check is the detection tool behind most of the paths below.

None of these are silent gaps we hope you don't notice — each is called out on the relevant framework page, in Limitations, or in Three ways to run raxit. This page just gathers them.

A mismatched --agent-id doesn't fail loudly

What it is. The --agent-id you pass to raxit run must equal the agent: field raxit init wrote into security.yaml. If they differ, the daemon governs a different principal than the one your agent presents, and calls resolve against the wrong policy — or no policy at all. It does not stop the run by default. This is the single most common first-run trap.

How to detect it. Watch for everything denying with deny.principal_mismatch — that reason code means the attested principal matches no rule's agent:, so no rule can ever apply (see Common issues and Reason codes). Confirm the id a call was bound to with raxit explain <call_id>.

How to close it. Drop --agent-id entirely and let raxit auto-derive the id, or copy the id straight out of security.yaml's agent: field. The Quickstart walks the exact trap.

Calling a tool object directly, past a framework-scoped autopatch

What it is. raxit.autopatch.install("langgraph") patches that framework's tool-dispatch path only. A call that bypasses that path — for example invoking a tool object directly instead of routing it through the framework — is not intercepted. install() with no argument patches every framework it detects, but the same rule holds: only calls that flow through a patched dispatch point are governed. (Concretely, a LangGraph agent that calls a tool directly through BaseTool, bypassing the graph, is governed by the LangChain patcher — but a call that flows through no patched path at all is not; see LangGraph (Python).)

How to detect it. install() returns {framework: [methods patched]} — check that the framework you rely on is listed. Then use the audit trail: a tool that executed with no matching record in raxit audit tail ran ungoverned.

How to close it. Prefer the full-process launcher (raxit run) or an argument-free install() so every detected framework is patched, and wrap any tool you invoke outside a framework with an explicit wrapper (below). See Three ways to run raxit for how the modes compose.

Vercel AI SDK: four telemetry-hook bypasses

What it is. The Vercel AI SDK's zero-code autopatch hooks the SDK's telemetry registry. Four documented conditions route around it, each silently:

  • experimental_telemetry: { isEnabled: false } disables the dispatcher entirely;
  • experimental_telemetry: { integrations: [...] } replaces the global registry raxit registered into;
  • a direct tool.execute() call outside generateText/streamText/Agent never passes through the dispatcher;
  • providerExecuted tools run server-side, with nothing local to intercept.

MODIFY is also not available at this hook — the SDK captures input before the wrapper runs.

How to detect it. Read the call sites: any of the four configurations above, or a tool call made outside the standard generation loop, is a bypass. As always, a missing raxit audit tail record for a call you expected to be governed confirms it.

How to close it. Wrap the tools you care about with governedTool() — the explicit wrapper is unaffected by all four bypasses and is the only way to get MODIFY on this framework. Full detail on the Vercel AI SDK page.

Mastra: only the regular Agent.generate()/stream() path

What it is. Mastra's autopatch governs the regular Agent.generate()/stream() path only. Not covered: the durable agent's direct execution sites, direct makeCoreTool() users, direct tool.execute() calls outside the agent loop, Mastra's internal AI-SDK-compat executeTools/runToolsTransformation family, and clientTools (whose execute is stripped server-side by design). The interception point (convertTools) is internal-marked, so an upstream Mastra change could move it.

How to detect it. If Mastra's internal API drifts, raxit's fail-closed startup self-check raises ChokepointDriftError and aborts raxit run rather than running ungoverned — so drift is loud, not silent. For the uncovered call sites, audit which path your agent actually uses, and check raxit audit tail for a record.

How to close it. Use an explicit governedTool() / wrapped createTool for any of the uncovered paths. See Mastra for the full scope note.

MCP servers: install order decides whether the handler is governed

What it is. MCP has no framework-wide dispatch point to patch at import time, so governance is opt-in and per-instance: you call installMcpAutoPatch(server) yourself. The intended order is patch first, then register your tools/call handler. If you register the handler first, raxit retroactively wraps the already-registered handler in place and emits a loud warning — the call is still governed, just wired in the wrong order. Only if raxit cannot reach the SDK's internal handler table (an SDK-version drift case) does it degrade to a warn-only no-op: the handler then runs ungoverned, but never silently.

How to detect it. Watch for the ordering warning on stderr. The audit trail is the reliable check either way: a tools/call that produced no decision record was not governed.

How to close it. Call installMcpAutoPatch(server) before you register the tools/call handler. The ordering contract is spelled out on the MCP servers page.

The general rule: in-process interception is only as strong as the process

What it is. Autopatch and install() work by patching functions inside your process. Code in that same process can always reach a tool by a path the patch doesn't cover — an unwrapped, direct call that bypasses governance entirely is invisible to raxit, as it is to any in-process hook. This isn't a raxit-specific weakness; it's the nature of in-process interception.

How to detect it. The audit trail is the ground truth: if a tool executed and left no record, it ran outside governance. Treat "a side effect happened with no decision behind it" as the signal.

How to close it. For a zero-tolerance setup — where every governed call must be explicit in the source rather than attached at startup — use explicit wrappers (governed_tool in Python, governedTool() in TypeScript). The wrapper supports every decision type on every framework, and because the governance is written at the call site, there is no dispatch path to route around. This is Mode 3 in Three ways to run raxit.

  • Three ways to run raxit — launcher, in-process autopatch, and explicit wrappers, and when each leaves a gap.
  • Frameworks — the full coverage matrix, with the per-framework honesty notes these paths come from.
  • Limitations & honesty — the broader list of what raxit does not do yet.
  • Reason codes — including deny.principal_mismatch.

On this page