AI agent security

Which runtime security tools integrate with LangChain or AutoGPT?

An integration is a hook that can deny before the side effect. LangChain Python, LangGraph JS, and the Vercel AI SDK have published wrappers. AutoGPT and CrewAI do not.

10 min read
In short: An integration is a hook that can deny before the side effect. LangChain Python, LangGraph JS, and the Vercel AI SDK have published wrappers. AutoGPT and CrewAI do not.

Which runtime security tools integrate with LangChain or AutoGPT?

The ones that sit on a hook the host actually exposes, and that can still deny before the side effect. A vendor page that says "LangChain integration" isn't enough. Ask which function runs between generated arguments and execute, and what happens on deny.

LangChain, AutoGPT, and CrewAI don't share a security API. LangChain Python can wrap a tool. LangGraph JS (StateGraph plus ToolNode) has a published Guard adapter. CrewAI can abort on PRE_TOOL_CALL. AutoGPT gives you the command or block run method, or the HTTP client those commands share. There's no published Arcjet adapter for AutoGPT or CrewAI. Use only the hooks that exist.

For more information about inbound and retriever screens in those SDKs, see prompt injection protection for LangChain, LlamaIndex, and the Vercel AI SDK. Don't treat that page as the integration guide.

Hook points that can still deny

The following table lists the hook that can still deny on each host:

HostHook that can denyObserves onlyPublished Arcjet helper
LangChain Python

Wrap the @tool function, arcjet.guard.langchain.guard_tool, or LangChain wrap_tool_call middleware

Callbacks such as on_tool_start

Yes: guard_tool after pip install "arcjet[langchain]"

LangChain JS

Wrap the tool func, or the HTTP client the tool calls

Callbacks

None. Wrap the tool func yourself. Don't import a LangChain JS helper.

LangGraph JS

guardTool on authored tool(), and guardToolNode in place on ToolNode

Graph hooks and interrupt()

Yes: @arcjet/guard/langgraph/v1. See the

LangGraph agent guard

Vercel AI SDK

guardTool around tool() execute

A tool with no execute, or a wrapped tool called without toolsContext

Yes: @arcjet/guard/vercel-ai/v7

AutoGPT

Command or block run, or a shared HTTP client

After-execute pluginsNone
CrewAI

PRE_TOOL_CALL raising HookAborted, or BaseTool._run

Hooks that log and return, and hooks that raise anything other than HookAborted

None

Other products sit on the same kinds of hooks: they wrap execute, they sit on the host hook, or they only see HTTP that happens to route through them. Rein is an in-code peer. Datadog AI Guard is an evaluator you call with prompts or tool payloads. Guardrails AI and NVIDIA NeMo Guardrails judge text. None of those replace object-level authorization in the handler. For more information about the layer map, see AI agent runtime security and runtime security for LLM applications.

LangChain

LangChain Python has a published Guard wrapper. Install the extra, wrap the tool you already declared, and pass that wrapped tool to the agent. Actor and inputs come from server-controlled RunnableConfig and from validated arguments, not from a string the model invented for identity.

from arcjet.guard import launch_arcjet, local_input, server_input
from arcjet.guard.langchain import guard_tool
from langchain_core.tools import tool
arcjet = launch_arcjet(key=ARCJET_KEY)
@tool
async def create_github_issue(title: str, body: str) -> str:
"""Open a GitHub issue in the support repo."""
await github.create_issue(title=title, body=body)
return "opened"
guarded_create_issue = guard_tool(
guard=arcjet,
tool=create_github_issue,
label="tools.github-create-issue",
actor=lambda config: config["configurable"]["user_id"],
inputs=lambda arguments, _config: {
"title": server_input.string(arguments["title"]),
"body": local_input.string(arguments["body"]),
},
)

Use the async Guard client with ainvoke() and the sync client with invoke(). Resolvers receive RunnableConfig and the validated arguments, which is why actor reads from config["configurable"]: a model that can write its own actor can pick a policy scope.

The wrapper raises two different errors, and treating them as one is the mistake worth avoiding:

  • ArcjetToolDeniedError is a real DENY. It carries the decision. Tell the caller and don't retry.
  • ArcjetToolUnavailableError is an evaluation that couldn't be completed: a deadline, a resolver that raised, or an ALLOW whose has_failed_open() is true. That's an operational failure worth alerting on, not a policy result.

guard_tool blocks on both by default. Set on_guard_error="allow" only where availability beats enforcement, such as a read-only lookup. A denial follows the wrapped tool's handle_tool_error behavior when you configured that on the tool.

If you don't want a vendor wrapper, wrap the function yourself and keep the same two moments: before the HTTP client, and on the result before you return it to the model. LangChain's own wrap_tool_call middleware is an around-hook for every tool. It isn't a policy engine. You still write the allow or deny. The Python how-to is How do I secure a LangChain Python agent?. The docs page is the LangChain agent guard.

LangChain JS has no published Guard package. Wrap the tool function:

import { tool } from "@langchain/core/tools";
import { z } from "zod";
const createGithubIssue = tool(
async ({ title, body }) => {
if (
!(await allowOutbound({ action: "github.create_issue", title, body }))
) {
return "Denied by policy";
}
const issue = await github.rest.issues.create({
owner: "acme",
repo: "support",
title,
body,
});
if (!(await allowOutput(JSON.stringify(issue.data)))) {
return "Denied by policy";
}
return issue.data.html_url;
},
{
name: "create_github_issue",
description: "Open a GitHub issue in the support repo",
schema: z.object({
title: z.string(),
body: z.string(),
}),
},
);

LangGraph JS is a different host. The Graph API adapter (StateGraph plus ToolNode) is documented at LangGraph agent guard. Import @arcjet/guard/langgraph/v1. That is not a generic LangChain JS package, and it is not Python create_agent. The how-to is How do I secure a LangGraph JS agent?.

LangGraph JS

LangGraph JS has a published Guard adapter. Screen inbound text with a direct guard() call before graph.invoke. Wrap authored tool() / StructuredTool with guardTool. Wrap ToolNode in place with guardToolNode so unwrapped and MCP tools hit Guard. interrupt() is a human hold, not a policy.

This is the Graph API only. It is not deprecated createReactAgent, and it is not Python LangChain create_agent. MCP tools execute inside ToolNode; graph hooks cannot stop tool.invoke. See How do I secure MCP tools in LangGraph?.

Vercel AI SDK

The Vercel AI SDK is the other published JavaScript wrapper. guardTool from @arcjet/guard/vercel-ai/v7 sits between generated arguments and execute:

import { launchArcjet, tokenBucket } from "@arcjet/guard";
import { guardTool } from "@arcjet/guard/vercel-ai/v7";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const issueLimit = tokenBucket({
bucket: "github-create-issue",
refillRate: 5,
intervalSeconds: 60,
maxTokens: 10,
});
const createIssue = guardTool(arcjet, createIssueTool, {
// Slug, past tense. Underscores and uppercase are rejected server-side.
action: "issue.opened",
// A callback over the parsed tool input, so the limit keys on the repo
// the model actually chose.
rules: ({ repo }) => [issueLimit({ key: `repo:${repo}`, requested: 1 })],
actor: (_input, context) => context?.userId ?? "anonymous",
onGuardError: "deny",
});

On DENY the tool's execute never runs and the model receives a structured result carrying reason, retryable, and, for a rate limit, retryAfterSeconds. Only rate-limit denials are retryable. Reshape that result with onDeny if the model needs different wording, and put a line in the system prompt telling it not to retry a denial, or it burns steps trying.

Two constraints are easy to trip. guardTool throws if the tool already declares its own contextSchema, because that's how the wrapper injects the run context. And the correlation context is optional at the type level, so forgetting toolsContext on the generateText call still compiles and leaves every check uncorrelated. Run once with ARCJET_LOG_LEVEL=warn and confirm the correlation before you ship.

onGuardError defaults to "deny", the opposite of a direct guard() call. For more information, see framework integrations. The action-gate how-to is How do I secure a Vercel AI SDK agent?. Inbound prompt-injection screens for this SDK live on the dedicated page.

AutoGPT

There's no @arcjet/guard/autogpt package.

AutoGPT Forge commands and AutoGPT Platform blocks both end at a run (or @command) body. That body is the hook. After-execute plugins run too late.

import httpx
async def run_http_command(url: str, payload: dict, actor: str) -> str:
if not allow_outbound(actor=actor, url=url, payload=payload):
raise PermissionError("Denied by policy")
async with httpx.AsyncClient() as client:
response = await client.post(url, json=payload, timeout=15)
text = response.text
if not allow_output(text):
raise PermissionError("Denied by policy")
return text

Put run_http_command behind every command or block that calls an external API. A tool that opens its own socket bypasses it, so the audit is a grep for httpx, requests, and aiohttp in the command directory. The allow functions are yours: rate limit, host allowlist, argument screen, object check. For more information about those checks, see runtime controls on external APIs.

CrewAI

There's no @arcjet/guard/crewai package. CrewAI already has a deny hook.

PRE_TOOL_CALL runs before every tool, and the tools argument narrows a hook to the ones you care about. Raise HookAborted to stop the call; the reason and source land in telemetry. POST_TOOL_CALL can replace the result string so injected text never returns to the model.

The failure mode to know is that CrewAI hooks fail open by design. CrewAI swallows any exception other than HookAborted so one buggy hook can't crash a run, which means a ValueError from your own code, or a timeout inside the SDK you call, lets the tool run. Wrap the check and convert anything you catch into HookAborted yourself.

from crewai.hooks import HookAborted, InterceptionPoint, ToolCallHookContext, on
@on(
InterceptionPoint.PRE_TOOL_CALL,
tools=["github_create_issue", "stripe_charge", "send_email"],
)
def screen_outbound_args(ctx: ToolCallHookContext) -> None:
if not allow_outbound(tool=ctx.tool_name, args=ctx.tool_input):
raise HookAborted(reason="Denied by policy", source="runtime-control")
@on(InterceptionPoint.POST_TOOL_CALL)
def screen_outbound_result(ctx: ToolCallHookContext) -> str | None:
if not ctx.tool_result:
return None
if not allow_output(ctx.tool_result):
return "[tool output blocked]"
return None

Mutate ctx.tool_input in place if you need to clamp a field. Don't replace the dict. The legacy @before_tool_call decorator still works: return False blocks, with a generic message and no abort reason.

If you authored the tool, you can also put the same checks in BaseTool._run. That covers one tool. The hook covers tools that you didn't write, including ones that a crew mounts later.

CrewAI task guardrail validates task output. It isn't an action gate on stripe_charge. Human approval through request_human_input is a hold, not a policy. For more information about that split, see human approval is not a security policy.

What counts as an integration

A runtime security tool integrates with a host when your code can call it on that host's hook and branch on allow or deny before the vendor request, the file write, or the MCP call. Logs after send() don't count. A web application firewall (WAF) on the chat route doesn't see the Stripe call. An MCP gateway doesn't see a local httpx post.

Use the preceding table. Then put the controls from runtime controls on external APIs on that hook: authorize the action, rate-limit the identity, screen arguments, screen output. For Model Context Protocol (MCP) tools with no local execute function, see secure MCP server and agent tool calls.

The OWASP Top 10 for LLM Applications still applies: prompt injection, excessive agency, and sensitive-information disclosure. Detection on the user bubble doesn't close a live GitHub or Stripe tool. A correlationId reconstructs the run. It doesn't deny a later step because of earlier ones.

If the framework doesn't expose a hook, the check still belongs in execute.

Frequently asked questions

Which runtime security tools integrate with LangChain or AutoGPT?

Tools that sit on a hook the host exposes and that can deny before the execute function. LangChain Python has a published guard_tool wrapper. LangGraph JS has @arcjet/guard/langgraph/v1. The Vercel AI SDK has @arcjet/guard/vercel-ai/v7. LangChain JS, AutoGPT, and CrewAI don't have a published Arcjet adapter; wrap the tool function, the command or block run method, or the shared HTTP client.

How to secure AI agents built with LangChain or the Vercel AI SDK

Wrap authored tools so that the check runs between generated arguments and the execute function. LangChain Python uses guard_tool. The Vercel AI SDK uses guardTool from @arcjet/guard/vercel-ai/v7. LangChain JS wraps the tool func yourself. Prompt-injection screens for those SDKs live on the dedicated prompt-injection page. Stack how-tos: How do I secure a LangChain Python agent, and How do I secure a Vercel AI SDK agent.

Is there an Arcjet adapter for AutoGPT or CrewAI?

No. There is no @arcjet/guard/autogpt or @arcjet/guard/crewai package. On AutoGPT, put the check in the command or block run method or in a shared HTTP client. On CrewAI, raise HookAborted from PRE_TOOL_CALL, or put the check in BaseTool._run.

Is there a published LangGraph adapter?

Yes. LangGraph JS Graph API (StateGraph plus ToolNode) uses @arcjet/guard/langgraph/v1. See the LangGraph agent guard docs and How do I secure a LangGraph JS agent. Generic LangChain JS still has no published helper; wrap the tool func yourself.

AI runtime security in your code

Protect your AI agent workflows with Arcjet

Get allow, deny, and redact on agent actions before the side effect.