flawopen.com/AI Security/langgraph-js-graph-input-control-directive-october-2026
LangGraph.js GHSA-wp87-994x-j95x: JSON Input as a Command
LangGraph.js <1.4.21 could treat plain JSON graph input as a control directive, skipping application processing or approval nodes. Upgrade and validate state at the server boundary.[1]
A graph is like a train station: ordinary passenger luggage should go through inspection before it boards. In affected LangGraph.js versions, a luggage tag that merely said “Command” could be mistaken for an authorized track-switch order. The fix distinguishes ordinary plain objects from Command objects, while the application still has to check every passenger’s destination and permission before a sensitive action.[1], [2]
Core Concepts & Subsystem Terms
GHSA-wp87-994x-j95x- LangChain's High-severity GitHub Security Advisory, published October 8, 2026. The advisory reports CVSS 4.0 7.6,
CWE-20, and no known CVE.[1] Graph input- The state object supplied to a LangGraph.js invocation. Applications can construct it from a request, but should validate and select its fields on the server.[1]
Command- A LangGraph.js object that can update graph state or direct execution. A plain JSON object must remain data, even if it contains a field named
lg_name.[2] Control-flow boundary- The point where application data is interpreted as instructions that select graph nodes or alter state.[1]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-20
Step-by-Step Attack Flow
An application accepts caller-controlled JSON
The affected deployment passes untrusted JSON directly to a graph instead of constructing a validated state object on the server.[1]
Normal graph processing can be skipped
The object may be handled as a state update or routing instruction, bypassing application-defined validation or approval nodes, as described by the advisory.[1]
Tool permissions can magnify the impact
If the graph exposes sensitive tools, the advisory says an attacker able to provide input could cause unauthorized tool execution with the application's permissions. Enforce authorization again at the operation boundary.[1]
Root Cause Analysis
The LangChain advisory covers @langchain/langgraph versions >=0.2.23 and <1.4.21. The vulnerable condition is not simply “using LangGraph”: an application must pass untrusted JSON directly as graph input and rely on the graph's normal execution path to enforce validation or authorization. The upstream fix explains that isCommand previously recognized a value from its lg_name field alone. Because JSON can contain that field, an ordinary parsed object could be mistaken for a Command. The graph could then apply its update or goto control behavior before application-defined processing nodes ran. The advisory says this could bypass validation or approval steps and, where sensitive tools are exposed, cause actions under the application's permissions. The issue is CWE-20; the advisory lists High severity and CVSS 4.0 7.6, and says no CVE is known. The fix in 1.4.21 rejects plain objects as Command instances while preserving actual Command instances, including instances from another installed copy of the package. This discriminator closes the reported JSON-to-control misclassification; it does not replace server-side state construction, field allowlisting, or authorization at each sensitive operation.[1], [2]
Source Code: Flaw vs. Secure Implementation
This reduced TypeScript illustration reflects the upstream change described in PR #2966: a lg_name field by itself no longer makes a plain object a Command. It is not a verbatim source excerpt or a complete graph implementation. A request body parsed as JSON produces plain data, so it follows the ordinary state path after the fix; genuine Command objects still support graph control flow. Do not use object shape as application authorization. Build state on the server from an allowlist, and authorize every sensitive tool operation independently.[2]
Source excerpt: Reduced behavioral illustration of the LangGraph.js Command discriminator; not the full upstream source
type CommandLike = { lg_name: string; goto?: unknown; update?: unknown };
function isCommand(value: unknown): value is CommandLike {
return typeof value === "object" && value !== null &&
"lg_name" in value && value.lg_name === "Command";
}
type CommandLike = { lg_name: string; goto?: unknown; update?: unknown };
function isPlainObject(value: object): boolean {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function isCommand(value: unknown): value is CommandLike {
return typeof value === "object" && value !== null &&
!isPlainObject(value) &&
"lg_name" in value && value.lg_name === "Command";
}
Engineering & System Hardening Checklist
- ✓Upgrade every affected
@langchain/langgraphinstallation to 1.4.21 or later; verify the resolved package version in the lockfile and deployed artifact.[1], [2] - ✓Do not pass request JSON directly to
graph.invoke()orgraph.stream(); create graph state server-side from an explicit allowlist of expected fields.[1] - ✓Keep graph-control objects separate from caller-controlled state, and add regression tests showing that JSON-parsed values are ordinary input even when they contain control-like field names.[2]
- ✓Authorize each sensitive tool or side effect at the operation boundary using the authenticated principal and resource scope; do not rely solely on an earlier graph node.[1]
- ✓Add an integration test that sends untrusted JSON through the graph and asserts that a sensitive-tool stub is never called unless the authenticated principal is authorized for the target resource.[1], [2]