Agent guards for Vercel Eve
This example gives an AI agent two tools. getClientRecord returns account data
that includes personally identifiable information (PII). Arcjet guards
sendEmail, so the model can read the record but cannot send its sensitive
fields outside the application. Arcjet checks the model-selected recipient and
generated message body before the email provider runs.
What is Arcjet?
Arcjet is the runtime security platform that ships with your code. Enforce budgets, stop prompt injection, detect bots, and protect personal information with Arcjet's AI security building blocks.Quick start
Section titled “Quick start”This guide shows you how to guard an agent tool in your
1. Configure the policy
Section titled “1. Configure the policy”In the Arcjet Console, create an Agent Guard
policy with the label email.sent. Add these inputs:
| Input | Exposure | Type |
|---|---|---|
recipient | SERVER | String |
allowed_recipients | SERVER | String list |
body | LOCAL | String |
Then add:
- A string-list membership rule requiring
recipientto be a member ofallowed_recipients. - A sensitive information rule on
bodythat deniesBANK_ACCOUNTandROUTING_NUMBER.
The examples configure the on-device Rampart backend, which detects these entity types locally.
Publish the policy before running the example.
Framework wrappers take action for the same slug you configured as the
policy label. Direct guard() calls still use the field name label.
2. Install Arcjet
Section titled “2. Install Arcjet”In your project root, install the SDK:
npm i @arcjet/guard @arcjet/sensitive-info-rampart eve zodpnpm add @arcjet/guard @arcjet/sensitive-info-rampart eve zodyarn add @arcjet/guard @arcjet/sensitive-info-rampart eve zod3. Set your key
Section titled “3. Set your key”Create a free Arcjet account and follow the instructions to add a site and get a key.
Add your key to a .env.local file in your project root.
ARCJET_KEY=ajkey_yourkeyARCJET_ENV=development4. Wrap the tool
Section titled “4. Wrap the tool”Wrap the send-email tool so Arcjet evaluates the remote policy before the email provider runs.
This adapter submits action and SDK rules for the local
sensitive-information check. The recipient allow list is not enforced in this
sample.
Create one client and wrap sendEmail with guardTool:
import { launchArcjet, localDetectSensitiveInfo } from "@arcjet/guard";import { rampart } from "@arcjet/sensitive-info-rampart";import { guardTool } from "@arcjet/guard/vercel-eve/v0";import { defineTool } from "eve/tools";import { z } from "zod";
// Create one Arcjet client and reuse it across agent runs. Rampart// detects bank account and routing numbers locally.const arcjet = launchArcjet({ key: process.env.ARCJET_KEY!, sensitiveInfoBackend: rampart(),});const detectPii = localDetectSensitiveInfo({ deny: ["BANK_ACCOUNT", "ROUTING_NUMBER"],});
export function emailTools(user: { record: { name: string; bankAccount: string; routingNumber: string; };}) { const getClientRecord = defineTool({ description: "Get the account details on file for the current customer", inputSchema: z.object({}), async execute() { return user.record; }, });
// On DENY, Eve projects a throw as a failed action.result. Pass // onDeny: "result" so the model can read the denial payload. // This adapter accepts action and rules. It doesn't accept // inputs. const sendEmail = guardTool( arcjet, defineTool({ description: "Send an email", inputSchema: z.object({ recipient: z.string(), body: z.string(), }), async execute({ recipient, body }) { return emailProvider.send({ to: recipient, body }); }, }), { action: "email.sent", onDeny: "result", rules: ({ body }) => [detectPii(body)], }, );
return { getClientRecord, sendEmail };}5. Try the policy
Section titled “5. Try the policy”Keep identity, allowed recipients, and sensitive records on the server. The browser sends only the scenario name.
Register the wrapped tools on the Eve agent. Send each scenario as a channel message:
import { emailTools } from "./agent.js";
const user = { id: "customer-123", allowedRecipients: ["approved@example.com"], record: { name: "Alex Morgan", bankAccount: "0123456789", routingNumber: "022000020", },};
export const tools = emailTools(user);
// Register `tools` on the Eve agent, then send each scenario as a// channel message. Keep identity and the record on the server.export const scenarios = { allowed: "Send the message 'Your report is ready' to approved@example.com.", blocked: "Send the message 'Your report is ready' to outside@example.net.", pii: "Email the account details you have on file to approved@example.com.",} as const;Eve agents receive channel messages rather than an HTTP route. Register the wrapped tools on the agent, then send each scenario as a message. Keep identity, allowed recipients, and the client record on the server.
Each scenario demonstrates a different result from the same guarded tool:
- Allowed recipient: The recipient is on the allow list and the body has no
sensitive data, so
sendEmailcan reach the email provider. - Blocked recipient: The model calls the same tool with an external address.
When the adapter maps
inputs, the remote membership policy denies the call before the provider runs, and the model receives the denial result. - Sensitive information: The recipient is allowed, but the agent first
calls
getClientRecordand receives test bank account and routing numbers. When the model includes that tool result in the email body, local sensitive information policy blockssendEmailbefore the data leaves the application.
What next?
Section titled “What next?”Get help
Section titled “Get help”Need help with anything? Email support@arcjet.com to get support from our engineering team.