Agent framework security: where each stack can deny

Compare Eve, Mastra, Claude Agent SDK, OpenAI Agents, LangGraph, and LangChain by which hook can still refuse a side effect.

11 min read
In short: Agent frameworks differ on which hook can still refuse a side effect. Compare Eve, Mastra, Claude Agent SDK, OpenAI Agents SDK, LangGraph JS, and LangChain Python by inbound screening, authored-tool deny, MCP deny, and the human-in-the-loop trap, with the Arcjet adapter for each.

How do agent frameworks differ on security?

Agent frameworks differ on which hook can still refuse a side effect before it happens. Eve, Mastra, Claude Agent SDK, OpenAI Agents SDK, LangGraph JS, and LangChain Python each expose a different inbound path, a different way to deny an authored tool, and a different answer for MCP and other tools that you didn't write. Eve, Mastra, Claude Agent SDK, and LangGraph JS can deny tools that you didn't wrap. LangChain Python denies only the tools that you name in a ToolPolicy, and on OpenAI Agents SDK, hosted tools, MCP, and handoffs aren't Arcjet deny points. If your agent relies on MCP or built-in tools, choose one of the first four frameworks or plan a separate control for those paths. Arcjet ships first-party adapters for 15 frameworks, including all six, so one centrally managed policy decides in code, in the path of the tool call, with no proxy to route traffic through.

Arcjet publishes this comparison. Framework details come from each framework's public documentation and from the Arcjet framework integrations documentation, reviewed on September 25, 2026.

The agent framework security page is the series hub, and each column in its table is a job: screening inbound text, denying an authored tool, denying an unwrapped or MCP tool, and the human-in-the-loop (HITL) trap. The dedicated How-tos name the helper for each host. For the vendor-layer map, see AI agent security platforms.

A helper name that works on one framework often doesn't exist on the next, so a team that copies the last repository's helper into a new one can end up with a check that never runs. guardInbound exists on Eve but not on OpenAI Agents or LangGraph, and guardHooks exists on Mastra and Claude Agent SDK but not on LangGraph. Compare frameworks by the job each hook does rather than by the identifier.

Coding agents don't need a framework adapter. For Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code, Arcjet enforces the same kind of policy through the hooks each agent already fires, installed through managed settings. For more information, see how to secure AI coding agents.

What does each stack deny?

Each framework can refuse work for a job only if it exposes a hook for that job, and the following table names the Arcjet helper for each hook on six stacks. An entry of "None" in the MCP column means the Arcjet adapter has no deny point for tools that you didn't write.

StackInbound screenAuthored-tool denyUnwrapped / MCP denyHITL trap
Eve

guardInbound on the channel, before the agent sees the text

guardTool on a local execute

guardApproval on the connection's approval field, because OpenAPI and MCP connections have no local execute

onAllow: "user-approval"
Mastra

guardProcessor on inputProcessors

guardTool on createTool execute

guardHooks on beforeToolCall

requireApproval
Claude Agent SDK

UserPromptSubmit through guardHooks({ inbound }), or guard_hooks in Python

guardTool on an authored tool(), or guard_tool on @tool in Python

PreToolUse through guardHooks

canUseTool, which a bare allowedTools entry skips

OpenAI Agents SDK

A direct guard() call before the run. There is no inbound hook

guardTool on FunctionTool.invoke in JavaScript, guard_tool on tool_input_guardrails in Python

None. Hosted tools, MCP, handoffs, and agents used as tools aren't Arcjet deny points

needsApproval and hosted MCP approval

LangGraph JS

A direct guard() call before graph.invoke or in the first node

guardTool on tool() or StructuredTool

guardToolNode in place on ToolNode

interrupt()
LangChain Python

An application guard() call, or HTTP protect() , before ainvoke

guard_tool, or ArcjetMiddleware with a ToolPolicy

Only the tools that you name in a ToolPolicy. Tools with no policy pass through unguarded

A human callback that you add. ArcjetCaptureHandler observes and can't deny

On OpenAI Agents, the SDK's own tool guardrails can attach to local MCP servers through toolInputGuardrails in the server options, according to the OpenAI Agents SDK guardrails guide. Those are OpenAI tripwires that you write yourself rather than Arcjet policy, and the same guide says hosted MCP tools, other hosted tools, and handoffs don't use that pipeline. For more information about that split, see OpenAI Agents guardrails vs Arcjet.

LangChain in JavaScript has its own adapter. guardTool wraps an authored tool, and guardMiddleware on wrapToolCall gates every model-selected tool call, including tools that you didn't wrap.

The Vercel AI SDK adapter combines guardTool with createAgentContext and aiToolsContext, and it has no separate inbound hook. For more information about that wrapper, see How do I secure a Vercel AI SDK agent?.

The same action-selected policy model covers eight other first-party adapters:

  • TanStack AI: guardMiddleware on onBeforeToolCall.
  • Google ADK: guardPlugin on beforeToolCallback in JavaScript, and guard_tool on before_tool_callback or guard_plugin on Runner plugins in Python.
  • Cloudflare Think: guardHooks on beforeToolCall.
  • Claude Managed Agents: guardEvents before events.send, and guardCustomTool on custom tools. Anthropic runs the built-in tools, so they have already run by the time your code sees the event.
  • CrewAI: register_arcjet_hooks on PRE_TOOL_CALL.
  • Genkit: guardTool on a ToolAction, and guardMiddleware on the tool hook.
  • Strands Agents: guardTool on an authored tool, and guardHooks on BeforeToolCallEvent.
  • Microsoft Agent Framework in Go: GuardTool and GuardTools on a tool.FuncTool, and GuardMiddleware on agent.Config.Middlewares.

For the full matrix, including import paths, see framework integrations.

What is the HITL trap on each stack?

Human-in-the-loop controls pause a call so that a person can approve it, but they don't evaluate a policy, and a run that nobody is watching stalls. Eve user-approval, Mastra requireApproval, and Claude canUseTool are covered in Human approval is not a security policy. OpenAI needsApproval, hosted MCP approval, and LangGraph interrupt() are covered in needsApproval and LangGraph interrupt() are not a security policy.

On Claude Agent SDK, the gap is concrete. The Claude Agent SDK permissions documentation states that a call approved by an allow rule or a permission mode skips the canUseTool callback, and recommends a PreToolUse hook for checks that must run on every tool call.

A reviewer who approves a run of lookups and one refund has also approved whatever the next call sends, and by then the model has already read the prompt. Screen inbound text first, deny the tool or connection before the framework calls it, and then reserve a human pause for the few irreversible actions that a policy allows.

What happens on each stack when Guard is unavailable?

Every framework wrapper fails closed when the policy can't be evaluated, but each stack reports that differently, and the shape decides what your error handling looks like. A denial means that the policy refused the call. An unavailable result means that the check didn't complete.

On the Vercel AI SDK, the tool doesn't execute and the model receives a retryable denial result with reason ERROR. LangChain Python separates the two cases by exception type: guard_tool raises ArcjetToolUnavailableError rather than ArcjetToolDeniedError, while guard_action and ArcjetMiddleware raise ArcjetUnavailableError rather than ArcjetDeniedError. CrewAI's register_arcjet_hooks raises the same HookAborted for a denial and for unavailability.

Eve, Mastra, Claude Agent SDK, LangGraph, and OpenAI Agents helpers all default to onGuardError: "deny". The OpenAI Agents guardTool returns a plain ArcjetDenialResult and doesn't throw, so the denial arrives in the tool output rather than as an error.

Inbound screening is the one place where "allow" is often defensible, because failing closed on a user's first message stops the agent answering at all during an outage. Set it deliberately for each call site, and never on a tool that sends data outward. A direct guard() call behaves differently again: it fails open and reports that on hasFailedOpen(), so an inbound screen that only checks for DENY still starts the agent during an outage.

Which Learning Center guide do I read for each framework?

Start with the security guide for the framework that you ship, then read the inbound or unwrapped-tool recipe if you need a single surface:

How do I pick a starting check?

If the framework has a channel or prompt-submit hook, screen that text before the turn starts. If you wrote the tool, wrap it. If the framework invokes MCP or a built-in tool that you didn't write, use that framework's unwrapped deny, and on OpenAI Agents plan for those paths to stay outside an Arcjet deny.

Use protect() on HTTP routes and guard() on tools, MCP handlers, and any path with no Request object. Don't construct a fake Request to reach protect() from a tool. Bot detection and Shield are HTTP rules, so they run on protect() only. A direct guard() call fails open, while the framework wrappers default to deny.

OpenAI Agents inputGuardrails are SDK tripwires rather than Arcjet checks. For more information about that split, see OpenAI Agents guardrails vs Arcjet. For more information about the two first-party SDKs, see OpenAI Agents SDK vs Claude Agent SDK. Adapter documentation lives under framework integrations.

Frequently asked questions

How do agent frameworks differ on security?

Agent frameworks differ on which hook can still refuse a side effect. Eve, Mastra, Claude Agent SDK, OpenAI Agents SDK, LangGraph JS, and LangChain Python each expose a different inbound path, a different way to deny an authored tool, and a different answer for MCP and other tools that you didn't write. Arcjet ships a first-party adapter for each of them.

Which frameworks can deny MCP or unwrapped tools?

With Arcjet, Eve uses guardApproval on the connection, Mastra uses guardHooks on beforeToolCall, Claude Agent SDK uses PreToolUse, and LangGraph JS uses guardToolNode. LangChain Python denies only the tools that you name in a ToolPolicy. On OpenAI Agents, hosted tools, MCP, and handoffs aren't Arcjet deny points.

Is human approval a security policy on any of these frameworks?

No. Eve user-approval, Mastra requireApproval, Claude canUseTool, OpenAI needsApproval, hosted MCP approval, and LangGraph interrupt() pause a call for a person. None of them evaluates a policy, and a run that nobody is watching stalls. Screen inbound text and deny the tool or connection first, then reserve a human pause for the few irreversible actions that a policy allows.

Where do I start on the framework I ship?

Start with the Learning Center security guide for the framework that you ship, then read its inbound or unwrapped-tool recipe if you need a single surface. For example, on Claude Agent SDK, read the security guide first, then the guides on screening inbound prompts and blocking Bash. The agent framework security page links each dedicated guide.

Does Arcjet detect bots on these agent paths?

No. Bot detection and Shield are HTTP rules that run on protect(), so they apply to HTTP routes rather than to tool calls. Use guard() for tools, MCP handlers, and paths with no Request object, and don't construct a fake Request inside a tool to reach protect().

What happens when Arcjet is unavailable on each framework?

Every framework wrapper fails closed by default with onGuardError: "deny", but each stack reports unavailability differently. The Vercel AI SDK returns a retryable denial with reason ERROR, and LangChain Python raises ArcjetToolUnavailableError or ArcjetUnavailableError. A direct guard() call fails open and reports it on hasFailedOpen().

Do coding agents need a framework adapter?

No. For Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code, Arcjet enforces policy through the hooks each agent already fires, with no SDK and no code change. An administrator installs the hooks through managed settings, so one policy applies across all five agents and developers can't remove it without admin access.

AI runtime security in your code

Protect your AI agent workflows with Arcjet

Arcjet runs inside your application, where it can use runtime context to enforce agent actions and budgets, detect prompt injection, and protect sensitive information before a workflow acts.