Skip to Content
FrameworksLangGraph

LangGraph

Static analysis for LangGraph applications to detect infinite state cycles, unbounded graphs, and missing termination conditions.

Quick Start

inkog scan ./my-langgraph-app

What Inkog Detects

FindingSeverityDescription
Graph CycleCRITICALStateGraph without exit condition loops forever
No Terminal NodeHIGHConditional edges without END node
Node TimeoutHIGHNode execution without time limits
Unbounded StateHIGHState accumulation without cleanup
Token BombingCRITICALCycles that trigger unlimited LLM calls

StateGraph Infinite Cycles

The most dangerous LangGraph vulnerability. A cycle without proper exit conditions runs indefinitely.

Vulnerable
No exit condition - graph cycles until timeout or crash
from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)

workflow.add_edge("agent", "tools")
workflow.add_edge("tools", "agent")  # Infinite cycle!

workflow.set_entry_point("agent")
# No END edge - graph never terminates

app = workflow.compile()
Secure
Conditional edge with iteration limit and completion check
from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)

def should_continue(state):
  if state["iterations"] >= 10:
      return "end"
  if state["task_complete"]:
      return "end"
  return "continue"

workflow.add_conditional_edges(
  "agent",
  should_continue,
  {"continue": "tools", "end": END}
)
workflow.add_edge("tools", "agent")

workflow.set_entry_point("agent")
app = workflow.compile()

Missing Terminal Nodes

Graphs without END nodes or with unreachable terminals never complete.

Vulnerable
Validate node has no exit path
workflow = StateGraph(State)

workflow.add_node("process", process_node)
workflow.add_node("validate", validate_node)

workflow.add_conditional_edges(
  "process",
  lambda x: "retry" if x["error"] else "done",
  {"retry": "process", "done": "validate"}
)
# validate has no outgoing edge!
# Graph state: stuck
Secure
All paths lead to END node
workflow = StateGraph(State)

workflow.add_node("process", process_node)
workflow.add_node("validate", validate_node)

workflow.add_conditional_edges(
  "process",
  lambda x: "retry" if x["error"] else "done",
  {"retry": "process", "done": "validate"}
)
workflow.add_edge("validate", END)  # Terminal node

# Or conditional termination
workflow.add_conditional_edges(
  "validate",
  lambda x: "end" if x["valid"] else "retry",
  {"end": END, "retry": "process"}
)

Recursion Limits

LangGraph has built-in recursion protection. Always configure it.

Vulnerable
No explicit limit on graph recursion depth
app = workflow.compile()

# Default recursion limit may be too high
# or not enforced in all cases
result = app.invoke(initial_state)
Secure
Configured recursion limit and interrupt points
app = workflow.compile()

# Explicit recursion limit
config = {"recursion_limit": 25}

result = app.invoke(
  initial_state,
  config=config
)

# Or set globally
from langgraph.pregel import Pregel
app = workflow.compile(
  checkpointer=memory,
  interrupt_before=["human_review"]  # Add approval gates
)

State Accumulation

Unbounded state growth causes memory issues and context overflow.

Vulnerable
State grows unbounded, exhausting memory
class AgentState(TypedDict):
  messages: list  # Grows forever
  context: str    # Accumulates

def agent_node(state):
  # Appending without limits
  state["messages"].append(new_message)
  state["context"] += additional_context
  return state
Secure
Bounded state with rolling window
from collections import deque

class AgentState(TypedDict):
  messages: Annotated[list, "max_length:20"]
  context: str

def agent_node(state):
  # Bounded message history
  messages = state["messages"][-20:]  # Keep last 20
  messages.append(new_message)

  # Truncate context
  context = state["context"]
  if len(context) > 4000:
      context = summarize(context)

  return {"messages": messages, "context": context}

Multi-Agent Cycles

Agent handoffs can create infinite loops between agents.

Vulnerable
Agents hand off indefinitely
def router(state):
  if "need_research" in state["task"]:
      return "researcher"
  if "need_writing" in state["task"]:
      return "writer"
  return "researcher"  # Default loops back!

workflow.add_conditional_edges(
  "researcher", router, {...}
)
workflow.add_conditional_edges(
  "writer", router, {...}
)
# Researcher → Writer → Researcher → ...
Secure
Handoff counter with maximum limit
def router(state):
  # Track handoff count
  handoffs = state.get("handoff_count", 0)
  if handoffs >= 3:
      return "end"

  if state["task_complete"]:
      return "end"

  if "need_research" in state["task"]:
      return "researcher"
  return "end"  # Default to termination

def increment_handoffs(state):
  state["handoff_count"] = state.get("handoff_count", 0) + 1
  return state

workflow.add_node("handoff_tracker", increment_handoffs)
workflow.add_conditional_edges(
  "handoff_tracker", router,
  {"researcher": "researcher", "writer": "writer", "end": END}
)

Streaming Without Limits

Streaming responses can accumulate tokens rapidly.

Vulnerable
No limits on streamed events or time
# Streaming all events without limits
for event in app.stream(inputs):
  if "messages" in event:
      for msg in event["messages"]:
          print(msg.content)
  # No stop condition
Secure
Event count and time limits
import time

MAX_EVENTS = 100
MAX_TIME = 60
start = time.time()
event_count = 0

for event in app.stream(inputs, config={"recursion_limit": 25}):
  event_count += 1

  if event_count > MAX_EVENTS:
      print("Event limit reached")
      break

  if time.time() - start > MAX_TIME:
      print("Time limit reached")
      break

  if "messages" in event:
      for msg in event["messages"]:
          print(msg.content)

Best Practices

  1. Always add END edges - Every path must terminate
  2. Set recursion_limit in config (recommended: 15-50)
  3. Use should_continue functions for conditional termination
  4. Track iteration count in state as a safety counter
  5. Bound state size - Truncate messages and context
  6. Add interrupt points for human-in-the-loop approval

CLI Examples

# Scan LangGraph project inkog scan ./my-langgraph-app # Focus on critical issues inkog scan . -severity critical # Generate HTML report inkog scan . -output html > report.html
Last updated on