Mastra
Governing Mastra agents in TypeScript, zero-code — including MODIFY.
Mastra's own tool conversion point — Agent.prototype.convertTools (verified against
@mastra/core@1.50.1; not re-exported from the package root, resolved from
@mastra/core/agent) — is the single point where every regular Agent's tool sources become
{ toolName: CoreTool }, each with its own execute. raxit run's preload post-wraps that
point, so every tool call resolves to permit / deny / MODIFY before it executes, with no
governance code in the tool definitions. TypeScript only.
Run it yourself
For your own Mastra agent, install the SDK and launch it under raxit run:
npm install @raxitlabs/raxit
raxit run -- node agent.mjsRun the same file without raxit run and it runs ungoverned — governance is a deploy-time
choice, not a code change.
How it works
Tools are ordinary createTool() definitions with no governance code:
// imports NOTHING from raxit
import { createTool } from "@mastra/core/tools";
import { Agent } from "@mastra/core/agent";
const send_email = createTool({
id: "send_email",
inputSchema: z.object({ to: z.string(), body: z.string() }),
execute: async ({ to, body }) => `sent to ${to}: ${body}`,
});
const agent = new Agent({ name: "mastra-bot", model, tools: { send_email } });
await agent.generate("send the receipt");apiVersion: raxit.security/v1
agent: mastra-bot
default: block
rules:
- effect: allow
resource: fetch_docs
- resource: send_email # MODIFY: redact PII in the body before sending
modify: { redact: [body] }
# shell_exec is intentionally UNLISTED -> default-deny (deny.default_or_forbid).MODIFY is on the autopatch path here — unlike the Vercel AI SDK's telemetry hook (which
falls back to governedTool() for MODIFY), Mastra's convertTools point passes input as the
first parameter to each converted tool's execute, so governance rewrites it before the
body runs — no explicit wrap needed.
Real captured output from a run:
raxit autopatch full demo · raw Mastra agent · daemon=http://127.0.0.1:8427
| raw Mastra agent · governance=ON (raxit run)
|
| [fetch_docs] OK -> received={"query":"refund policy"}
| [shell_exec] BLOCKED -> body never ran
| [send_email] MODIFIED -> received={"to":"a@b.c","body":"charge card [REDACTED:pii_credit_card]"}
PASS governance active (RAXIT_AUTOLOAD propagated by raxit run)
PASS fetch_docs -> permit (ran)
PASS deny.default_or_forbid: shell_exec -> deny (body never ran)
PASS modify.policy: send_email -> ran on redacted body (PII stripped)
audit.verify -> ok=true signatures_verified=true signed_count=5 count=5
PASS audit.verify: signed Decision Provenance chain verified green
ALL PASSMastra's generation loop catches a denied tool's thrown RaxitDenied as a tool-error output
to the model, not a top-level exception a raxit-unaware agent can inspect directly — the agent
can only observe "body never ran" from its own call log. The demo's harness (entitled
to be raxit-aware) additionally calls gov.decide(...) directly to name the reason codes
explicitly.
Honest coverage scope: the regular Agent.generate()/stream() path only
Not covered by this autopatch, and needing an explicit governedTool() /
wrapped-createTool instead: 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, so there is no local body to govern).
convertTools is internal-marked, so drift risk is high — raxit's fail-closed startup
self-check (it raises ChokepointDriftError on drift and aborts raxit run rather than
running the agent ungoverned) is not optional in production.
What got denied/modified and why
deny.default_or_forbid—shell_execappears nowhere in the policy, so it falls todefault: block. The tool body never ran.modify.policy—send_emailmatched themodify: { redact: [body] }rule; the tool ran, but on the engine's rewritten arguments — the rawbody(containing card PII) was redacted to[REDACTED:pii_credit_card]before the tool ever saw it.
Every decision — permit, deny, and modify — lands in one hash-chained, Ed25519-signed Decision
Provenance log, verified green with client.audit.verify({ signatures: true, pubkeys: [...] }).
See Audit and Decisions for the full
permit / defer / deny / modify vocabulary and reason-code catalog.
Related
- Vercel AI SDK — a TS autopatch case where MODIFY is not
supported on the autopatch path (must fall back to
governedTool()) — the contrast case for this page. - LangChain.js / LangGraph.js — MODIFY at the
BaseToollayer instead of a framework-conversion point. - MCP servers — a per-instance (opt-in) autopatch hook, proving autopatch breadth beyond process-wide prototype patching.

