FlawOpen

flawopen.com/AI Security/langgraph-js-graph-input-control-directive-october-2026

● CWE-20 · CVSS v4 7.6[1] · High
FlawOpen Security Research

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]

CVSS vectorCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N[1]
💡 Plain English Explainer (ELI5)

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

Step 1

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]

Step 2

A data object is mistaken for a control object

Before 1.4.21, the lg_name marker alone could make a plain object look like a LangGraph Command. The issue is in type recognition, not in JSON parsing itself.[1], [2]

Step 3

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]

Step 4

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

UNPATCHED FLAW
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";
}
HARDENED SECURE PATCH
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

Sources

← AI Security Hub Directory →