Skip to content

Tool security ​

A tool crosses from model-generated intent into application code. Treat its handler as a product security boundary.

Authorize every operation ​

The model can propose valid arguments for data it should not access. Check user and tenant permissions inside the handler before reading data or causing a side effect.

ts
const requestRefund = createTool({
  name: 'request_refund',
  description: 'Create a refund request for an eligible order.',
  input: z.object({
    orderId: z.string(),
    amountCents: z.number().int().positive(),
    reason: z.string().min(1),
  }),
  async execute(args) {
    await auth.requireRefundPermission(user.id, args.orderId)
    await policy.assertRefundAllowed(args.orderId, args.amountCents)

    const refund = await refunds.createRequest({
      ...args,
      requestedBy: user.id,
    })

    await audit.record('refund.requested', {
      refundId: refund.id,
      orderId: args.orderId,
    })

    return `Refund request ${refund.id} was created.`
  },
})

Protect side effects ​

Keep each action narrow. Make retried operations idempotent where possible, validate business rules immediately before execution, and record sensitive changes in an audit log.

Use human approval for high-risk side effects, but never use approval as the only permission or policy check.

Before shipping ​

  • Keep the input schema narrow and documented.
  • Enforce user and tenant permissions in the handler.
  • Make side effects idempotent or audited.
  • Map private service failures before returning them.
  • Exclude internal fields and secrets from output.
  • Test the handler without a provider call.

Built for Anvia.