How do you secure Vercel AI SDK?
To secure a Vercel AI SDK application, screen text before generation, wrap authored tools with guardTool, and pass conversation context through aiToolsContext. Use guardAction for application-invoked work. Authorize resources with trusted caller identity; a clean prompt or a model-selected tool does not grant permission.
Use this guide on the Vercel AI SDK 7 (ai >=7 <8, @ai-sdk/provider-utils >=5 <6). AI SDK 7 requires Node.js 22 or later and ESM-only imports – CommonJS require() doesn't work.
The Vercel AI SDK is a toolkit for calling models and defining tools in TypeScript. When you call generateText or streamText, the model may invoke tool({ execute }) handlers you registered – send email, charge a card, call an internal API. User prompts, RAG chunks, and tool outputs can all contain text that tries to steer those calls. That is prompt injection.
Arcjet Guard is a runtime policy layer for AI agents. You define rules – prompt-injection detectors, rate limits, PII checks, allowlists – and Guard allows or denies inbound text and tool calls before side effects run. The following examples use Guard for both controls. They wrap AI SDK tool({ execute }) handlers only – not Mastra createTool, Eve defineTool, LangGraph tool(), or other frameworks' tool types.
Use this checklist to review your application:
- Patch and lock dependencies. Stay on AI SDK 7 and Node 22+.
- Keep API keys off the client and out of
NEXT_PUBLIC_variables. - Screen user text before
generateText/streamText. - Wrap model-invoked tools before
executeruns. Correlate the run. - Don't declare
contextSchemaon a tool that an action gate wraps. - Gate app-invoked work separately from model-invoked tools.
- Validate arguments. Scan free-text notes, not opaque IDs.
- Catch issues in the editor and in CI before they ship.
For a shorter wiring recipe, see How do I secure a Vercel AI SDK agent?. For the Guard helpers used in the examples, see the Vercel AI SDK agent guard. Prompt injection protection for LangChain, LlamaIndex, and Vercel AI SDK only places detectors on the route and the retriever – a clean inbound score still isn't authorization for sendEmail.
How do you keep Vercel AI SDK dependencies safe?
Apply patches on a schedule you actually keep, and out of band for a critical advisory. Commit a lockfile so every environment installs the same graph, and turn on Dependabot, Renovate, or an equivalent bot for npm. Run npm audit (or your package manager's audit) in CI and fail on known high-severity issues you haven't waived.
Pin ai and every @ai-sdk/* package together so a provider major can't drift under the core SDK. AI SDK 7 is ESM-only: set "type": "module" or use .mjs, and don't load it through CommonJS require().
Node.js 18 and 20 are unsupported – Node 20 reached end-of-life on 2026-04-30. Node 22 is Maintenance LTS until 2027-04-30; prefer Node 24 LTS for production. Node 26 is Current, not LTS. Put the floor you choose in engines.node so installs fail loudly on an unsupported runtime.
Treat added provider packages as review events: they often gain network or filesystem access. Prefer a short function that you maintain over a trivial one-liner package. See trivial packages.
How do you manage Vercel AI SDK secrets?
Model and gateway keys such as OPENAI_API_KEY and AI_GATEWAY_API_KEY belong on the server, loaded at process start. In a Next.js app, anything named NEXT_PUBLIC_* is inlined into the client bundle – anyone can read it – so never put a signing key or other server secret there. See the Next.js security checklist if the agent lives in a Next.js route.
Don't log tool arguments or the raw prompt. A support dump that includes the last user message is a leak. Prefer a secrets manager in production, and redact before records leave the process. See storing secrets in environment variables and redacting sensitive data from logs.
How do you screen inbound Vercel AI SDK text?
The AI SDK doesn't expose a first-class inbound hook, so screen in your route handler, job, or webhook before you call generateText or streamText. That is where a user prompt or retrieved chunk is still plain text – not yet part of the model's context.
With Guard, call arcjet.guard() with prompt-injection rules on that string. Launch the client once at module scope. Create one createAgentContext at the HTTP route, job, or webhook that starts the run – don't stash it in module state or AsyncLocalStorage. Direct guard() fails open: an ALLOW isn't proof the rules ran, so gate on decision.hasFailedOpen() when this call site must fail closed. On deny, don't start generation.
import { detectPromptInjection } from "@arcjet/guard";import { createAgentContext } from "@arcjet/guard/vercel-ai/v7";import { arcjet } from "./arcjet.js";
const context = createAgentContext({ correlationId: conversationId });const inbound = detectPromptInjection();
const decision = await arcjet.guard({ label: "message.received", rules: [inbound(prompt)], correlationId: context.correlationId,});
if (decision.conclusion === "DENY" || decision.hasFailedOpen()) { throw new Error("message blocked");}A caller-supplied correlationId must be 1-256 characters of printable ASCII. Prefer a conversation ID, request ID, or ticket ID over a generated value.
How do you gate Vercel AI SDK tools and actions?
After inbound screening, side effects happen when the model calls a tool – or when your application performs a risky step on its own. Guard's AI SDK helpers cover three callers:
guardTool()for model-invoked work. The LLM decided to call the tool.guardAction()for app-invoked work. Your code performs the risky step.captureAction()for observe-only records. No decision.
The wrapped tool must have execute – a tool with none has nothing to wrap. It also can't already declare contextSchema, because Guard uses that slot and throws if the tool already owns it.
On deny the email provider never runs and the model receives an ArcjetDenialResult. Don't throw from guardTool: a throw becomes a generic tool error and drops the fields. guardAction throws so application code can catch – ArcjetDeniedError on a deny, ArcjetGuardUnavailableError when the policy could not be evaluated.
import { launchArcjet, policyInput, tokenBucket } from "@arcjet/guard";import { aiToolsContext, createAgentContext, guardTool,} from "@arcjet/guard/vercel-ai/v7";import { generateText, tool } from "ai";import { z } from "zod";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });const emailLimit = tokenBucket({ bucket: "email", refillRate: 5, intervalSeconds: 60, maxTokens: 5,});
export async function runAgent( user: { id: string; allowedRecipients: string[] }, prompt: string, conversationId: string,) { const sendEmail = guardTool( arcjet, tool({ description: "Send an email", inputSchema: z.object({ recipient: z.string().email(), body: z.string(), }), execute: ({ recipient, body }) => emailProvider.send({ to: recipient, body }), }), { action: "email.sent", actor: user.id, rules: () => [emailLimit({ key: user.id, requested: 1 })], inputs: ({ recipient, body }) => ({ recipient: policyInput.server.string(recipient), allowed_recipients: policyInput.server.stringList( user.allowedRecipients, ), body: policyInput.local.string(body), }), }, );
const tools = { sendEmail }; const context = createAgentContext({ correlationId: conversationId });
return generateText({ model: "openai/gpt-4o-mini", prompt, tools, toolsContext: aiToolsContext(context, tools), });}Derive actor from an authenticated server-side identity. A policy can be conditioned on the actor, so an actor taken from model-produced tool input lets whoever controls the prompt escape their own policy scope.
If you omit toolsContext, Guard still runs but the call is uncorrelated. The first such call always warns. Omitting rules still makes the guard call so a remote policy can apply.
AI SDK 7 human-approval primitives aren't a remote policy. See human approval is not a security policy.
How do the editor and CI catch Vercel AI SDK security bugs?
Turn on TypeScript strict, ESLint, and secret scanning in the editor. Trunk, Semgrep, TruffleHog, and Gitleaks all have editor plugins and CI jobs. Run the same scanners in CI that you run locally – an editor warning that isn't a CI failure will be ignored.
Fail the build on secrets in the diff, on known vulnerable dependencies, and on CommonJS require("ai") if your repo is ESM-only. None of these replace a review of which tools have execute and which ones you actually wrapped. They catch the mistakes that are cheap to find automatically.
How do you verify enforcement before deployment?
Use deterministic Guard responses and a handler call counter. A DENY or unavailable evaluation must leave the counter at zero; an authorized ALLOW must call the handler once. Also test another tenant's resource identifier: application authorization must still refuse access even when Guard allows.
Test the wrapped execute directly and through a model run, then exercise application-invoked work guarded with guardAction. Confirm the published policy consuming allowed_recipients is active: supplying typed inputs alone does not enforce an allowlist. Reuse the conversation ID across turns while retaining the authenticated user as the actor.
What do you do after this guide?
Confirm each item against one sensitive route: a prompt that asks to email finance, an authored sendEmail that you wrapped, and an app-invoked GitHub review that uses guardAction. Then apply the Next.js security checklist if the route is a Next.js handler, and AI agent runtime security for sequence, budget, and identity controls.
Frequently asked questions
What is the Vercel AI SDK security guide?
To secure a Vercel AI SDK application, screen text before generation, wrap authored tools with guardTool, and pass conversation context through aiToolsContext. Use guardAction for application-invoked work. Authorize resources with trusted caller identity; a clean prompt or a model-selected tool does not grant permission.
Can I wrap a Mastra or Eve tool with this adapter?
No. The v7 adapter is for AI SDK tool({ execute }) only. Mastra, Eve, LangGraph, OpenAI Agents, Claude Agent SDK, Claude Managed Agents, Genkit, Google ADK, Strands, TanStack AI, and official CrewAI each have their own adapter.
What happens if I omit toolsContext?
Guard still runs but the call is uncorrelated. The first uncorrelated call warns. Pass aiToolsContext(context, tools) on generateText.
Should actor come from a tool argument?
No. Derive actor from an authenticated server-side identity. An actor taken from model-produced input lets whoever controls the prompt escape their own policy scope.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Wrap authored tools with guardTool so generateText cannot reach a side effect the policy would refuse.