StateGraph plus ToolNode: screen before invoke, wrap authored tools, and wrap ToolNode in place. Work through the full checklist in the LangGraph security guide.How do I secure a LangGraph JS agent?
Screen inbound text with a direct guard() call before graph.invoke, or in the first graph node. Wrap authored tool() / StructuredTool with guardTool. Wrap the ToolNode in place with guardToolNode so unwrapped and MCP tools hit Guard. interrupt() is a human hold, not a policy.
The full checklist is the LangGraph security guide. This page is the wiring recipe for the Graph API.
This adapter is StateGraph plus ToolNode. It isn't deprecated createReactAgent. It isn't Python create_agent. It isn't LangChain JS createAgent / wrapToolCall (@arcjet/guard/langchain/v1). The host table that also covers AutoGPT and CrewAI is Which runtime security tools integrate with LangChain or AutoGPT?.
protect() is the HTTP check on a route. The product map is the LangGraph agent guard.
What do I install and import?
Install @arcjet/guard alongside LangGraph, then import the helpers from the versioned path:
npm install @arcjet/guard @langchain/langgraph @langchain/coreImport from @arcjet/guard/langgraph/v1. There is no unversioned alias, so @arcjet/guard/langgraph doesn't resolve. The version segment tracks LangGraph's major, and @langchain/langgraph (>=1 <2) plus @langchain/core (>=1 <2) are optional type-only peers. The integration needs Node.js 22 or later. Launch one client at module scope with launchArcjet.
The integration exposes three surfaces: guardTool() for authored tools, guardToolNode() for the node that runs everything else, and langgraphAgentContext() for correlation. An integration skill ships in the package, so cp -r node_modules/@arcjet/guard/skills/integrate-arcjet-guard-langgraph ~/.claude/skills/ hands a coding agent the same recipe.
How do I screen inbound text?
LangGraph has no first-class channel, so there is no guardInbound. Put prompt-injection rules in the application before graph.invoke, or in the graph's first node. On deny, don't call invoke. See the inbound sample in the LangGraph security guide.
Direct guard() fails open. Gate on decision.hasFailedOpen() when this call site must fail closed.
Why isn't interrupt() a security policy?
interrupt() and interrupt_before=["tools"] pause the graph for a person. That is human-in-the-loop, not a remote allow or deny. See needsApproval and LangGraph interrupt() are not a security policy.
Graph hooks and HITL pauses can't stop tool.invoke inside ToolNode. The deny for those tools is guardToolNode.
How do I gate authored tools vs ToolNode tools?
guardTool wraps a LangChain tool() / StructuredTool. On deny the tool never runs. It returns a plain ArcjetDenialResult. ToolNode wraps that object into a ToolMessage whose status is success. Read arcjetDenied on the payload. See the wrap samples in the LangGraph security guide.
Unwrapped and MCP tools execute inside ToolNode. Use guardToolNode for those. Wrap the node in place; a copy leaves the original unguarded. See How do I secure MCP tools in LangGraph?.
A graph that only wraps lookup_order still posts a Linear comment if that tool arrived through MCP. interrupt() doesn't sit inside tool.invoke. Graph hooks don't sit there either. The last reversible point for a tool that you didn't author is guardToolNode on the node that you pass to StateGraph.addNode("tools", ...).
Don't also wrap these tools with @arcjet/guard/vercel-ai/v7, and never call createAgentContext inside a LangGraph callback. langgraphAgentContext() reads configurable.thread_id, then the run id, then configurable.checkpoint_ns. It never mints an id. It never calls createAgentContext. Pass the checkpointer thread_id that you already have on graph.invoke(input, { configurable: { thread_id } }). If none is a valid 1-256 printable-ASCII string, the call is uncorrelated rather than joined to a generated id. The correlation id reconstructs one run; it isn't an identity, and it isn't a policy key.
Is this the same as LangChain Python?
LangGraph JS isn't LangChain Python. LangChain Python uses guard_action on a callable, guard_tool on a BaseTool, and ArcjetMiddleware plus ToolPolicy on create_agent. For more information about that adapter, see How do I secure a LangChain Python agent?.
LangGraph JS is StateGraph plus ToolNode. Python create_agent and LangChain JS createAgent / wrapToolCall are different runtimes. Don't import @arcjet/guard/langgraph without the /v1 segment. For more information about the stack map, see agent framework security.
Frequently asked questions
How do I secure a LangGraph JS agent?
Screen inbound text with a direct guard() call before graph.invoke, or in the first graph node. Wrap authored tool() / StructuredTool with guardTool. Wrap ToolNode in place with guardToolNode for unwrapped and MCP tools. interrupt() is HITL, not policy.
Is this createReactAgent or LangChain Python?
No. This adapter is StateGraph plus ToolNode. It isn't deprecated createReactAgent. It isn't Python create_agent. It isn't LangChain JS createAgent / wrapToolCall (@arcjet/guard/langchain/v1).
Why must guardToolNode wrap ToolNode in place?
ToolNode's constructor captures func as an arrow bound to the instance, and run reads this.tools. A copy with a fresh tools array leaves the original executing unguarded tools.
Is there a guardInbound for LangGraph?
No. LangGraph has no first-class channel. Screen inbound with a direct guard() call before graph.invoke or in the first node. Direct guard() fails open; wrappers default to deny.
Where do I import the LangGraph helpers?
Run npm install @arcjet/guard @langchain/langgraph @langchain/core and import from @arcjet/guard/langgraph/v1. There is no unversioned @arcjet/guard/langgraph alias. Don't also wrap these tools with @arcjet/guard/vercel-ai/v7.
What happens if a rules callback throws?
The action, rules, and metadata factories are try/caught. A factory that throws counts as a guard error, so the fail-closed default stops the tool instead of running it with no decision.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Screen inbound text before the graph runs, and wrap ToolNode in place so MCP and unwrapped tools cannot execute past a deny.