Mastra runtime authorization
Wrap Mastra agents, tools, and workflows with Veto. Each governed tool call is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.
Why Mastra needs guardrails
Mastra is a TypeScript path from prompt to agent. Define a createTool with a Zod input schema, attach it to an Agent, and the model can call it. The schema validates argument shape. It does not validate intent. When a model decides to call delete_customer with cascade: true, the schema says "valid input" and Mastra calls execute().
Mastra's workflow engine has a larger action surface. Workflows chain steps with retries and conditional branches. A single prompt injection can travel through three steps before hitting a side effect that does not reverse. You need guardrails at each step boundary, not just at the workflow entry. See why prompt instructions are not guardrails.
Zod validates types and ranges. It cannot express "no cascade deletes in production" or "refunds over $500 need finance approval".
Mastra workflows chain tool calls. Without per-step guardrails, a poisoned intermediate result drives a destructive final step.
Mastra's telemetry captures observability. It does not produce a decision record for SOC 2 or HIPAA review.
Before and after Veto
The left tab shows a standard Mastra agent. The model picks a tool and Mastra's executor runs the body unconditionally. The right tab adds Veto inside each tool's execute function. Same agent, same tools, each governed call evaluated against policy first.
import { Mastra } from '@mastra/core'
import { Agent } from '@mastra/core/agent'
import { createTool } from '@mastra/core/tools'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
const deleteCustomer = createTool({
id: 'delete_customer',
description: 'Delete a customer record from the database',
inputSchema: z.object({
customer_id: z.string(),
cascade: z.boolean().optional(),
}),
execute: async ({ context }) => {
return await db.customers.delete(context.customer_id, { cascade: context.cascade })
},
})
const refundPayment = createTool({
id: 'refund_payment',
description: 'Refund a customer payment',
inputSchema: z.object({
payment_id: z.string(),
amount_cents: z.number(),
}),
execute: async ({ context }) => {
return await stripe.refunds.create({
payment_intent: context.payment_id,
amount: context.amount_cents,
})
},
})
const supportAgent = new Agent({
name: 'support-agent',
instructions: 'You handle customer support including refunds and account changes.',
model: openai(process.env.OPENAI_MODEL!),
tools: { deleteCustomer, refundPayment },
})
const mastra = new Mastra({ agents: { supportAgent } })
// The model picks deleteCustomer with cascade: true on the wrong record.
// Mastra calls execute(). The customer and their orders are gone.
const response = await mastra.getAgent('supportAgent').generate(userMessage)Mastra workflow steps
Workflow steps are guarded the same way as agent tools. Call veto.guard() at the top of each step's execute body. A denial throws; Mastra's retry and branching behavior stays intact.
import { Workflow, Step } from '@mastra/core/workflows'
import { Veto } from 'veto-sdk'
import { z } from 'zod'
const veto = await Veto.init({ apiKey: process.env.VETO_API_KEY })
const sendOutreach = new Step({
id: 'send-outreach',
inputSchema: z.object({
recipient: z.string(),
template: z.string(),
}),
execute: async ({ context }) => {
const decision = await veto.guard('send_outreach', context.inputData, {
user_role: context.user?.role,
org_id: context.org?.id,
})
if (decision.decision !== 'allow') {
throw new Error(`Outreach blocked: ${decision.reason}`)
}
return await mailer.send(context.inputData.recipient, context.inputData.template)
},
})
const outreachWorkflow = new Workflow({
name: 'outreach-workflow',
triggerSchema: z.object({ leads: z.array(z.object({ email: z.string() })) }),
}).step(sendOutreach)
// Workflow steps are guarded individually: pause at the first denial.
outreachWorkflow.commit()Policy configuration
Author guardrail rules in declarative YAML. Match on tool name, constrain arguments, set actions. See the runtime authorization glossary for terminology.
rules:
- name: protect_production_deletes
description: Block customer deletes outside dev
tool: delete_customer
when: context.environment != "development"
action: deny
message: "Customer deletes require dev environment or manual override"
- name: prevent_cascade_deletes
description: Cascade needs guardrails for agents
tool: delete_customer
when: args.cascade == true
action: deny
message: "Cascade deletes must be issued by a human operator"
- name: cap_refund_amount
description: Refunds over $500 require finance review
tool: refund_payment
when: args.amount_cents > 50000
action: require_approval
approvers: [finance-team]
timeout: 1h
- name: outreach_domain_allowlist
description: Only contact verified lead domains
tool: send_outreach
when: "!args.recipient.match(/@approved\\.invalid$/)"
action: deny
message: "Outreach restricted to allowlisted domains"How Veto fits
Install the SDK
npm install veto-sdk @mastra/coreDefine policies
Create veto/policies.yaml with rules for each tool ID. Reference the same ID used in createTool.
Guard each tool execute
Call await veto.guard() at the guarded execute bodies. Mastra agents, workflows, and memory stay outside the policy surface.
Use cases
Support agent refunds
Mastra support agents handle Stripe refunds. Cap refunds at $500 auto-approve, route larger amounts to finance, record governed actions for chargeback disputes.
Outbound outreach workflows
Mastra workflows generate and send sales outreach. Restrict recipient domains, cap send volume per hour, and block opted-out lead contact on the governed path.
Database write tools
Mastra tools that mutate the database get policy-level protection. Block cascade deletes, restrict schema changes to dev, require approval for production migrations.
Decision record
Guarded calls emit decision records when configured. Queryable via API, exportable for SOC 2 evidence. Mastra telemetry stays for observability, Veto handles the compliance log.
Frequently asked questions
Does Veto work with Mastra workflows and agents?
How does Veto interact with Mastra memory?
What about Mastra's built-in evals?
Can I share policies across multiple Mastra agents?
Related integrations
Wrap one Mastra tool path and inspect the decision record.