Forum

Notifications
Clear all

Switched from LangChain's stuff to LangGraph, auth story is still missing.

1 Posts
1 Users
0 Reactions
7 Views
(@hardener_leo)
Eminent Member
Joined: 2 months ago
Posts: 20
Topic starter   [#1918]

I've been evaluating LangGraph for a potential production deployment after moving away from LangChain's more fragmented orchestration approach. The structured state graphs and checkpointing are a significant improvement for control flow, but from a security standpoint, the authentication and authorization model for the graph itself and its tools appears to be an afterthought. This is a critical gap when you're deploying these graphs as long-running, stateful services that may handle sensitive data or interact with external APIs.

The core issue is that a LangGraph essentially becomes an execution engine for a graph of tools and LLM calls. The graph's state can be checkpointed to an external store (Redis, Postgres), and that state can be resumed by any caller who has the graph ID. Where is the mandatory authz check before resuming a state that might contain PII, internal reasoning, or tool outputs? There isn't one. The `configurable` fields are for routing logic, not for attaching principal or tenant identifiers in a validated way. You're meant to roll your own wrapper and hope you don't miss an edge.

Similarly, tool nodes are just Python callables. If your graph uses a `ToolNode` or a function binding, there is no built-in mechanism to enforce that the *caller* of the graph is authorized to trigger the specific tool (e.g., "send_email", "query_database"). You have to bake the authorization into the tool function itself, which violates clean separation and is easy to get wrong. The graph's security is only as strong as the weakest tool's ad-hoc checks.

Here's a trivial example of the problem. A graph with a state that includes a user-provided query, checkpointed to a public URL.

```python
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.sqlite import SqliteSaver

class State(MessagesState):
user_query: str

builder = StateGraph(State)
# ... define nodes, edges
memory = SqliteSaver.from_conn_string(":memory:")
graph = builder.compile(checkpointer=memory)

# First call, checkpoint is created.
config = {"configurable": {"thread_id": "thread_123"}}
initial_result = graph.invoke({"user_query": "show me all users' emails"}, config)
# State is now saved.

# Later, ANYONE can resume this exact conversation state if they can guess or discover the thread_id.
malicious_resume = graph.invoke({"user_query": "now change the admin password"}, config)
# The graph continues, with no authentication barrier.
```

The mitigation isn't complicated, but it must be systematic and enforced. My checklist for the team so far:

* **Wrap the graph invocation.** All `graph.invoke`, `graph.stream`, `graph.batch` calls must go through a middleware that validates a JWT or session token, extracts a principal, and validates it against the `thread_id` or a mapping table before allowing the call to proceed.
* **Isolate checkpoint stores.** The checkpoint storage (e.g., Redis DB) must be namespaced by tenant and inaccessible cross-tenant. The `thread_id` must be scoped with a tenant prefix.
* **Instrument tool nodes.** Every tool callable should receive validated principal data from the invocation context, not from the untrusted state. Implement a decorator that enforces a policy before execution.
* **Audit LangSmith.** If you're using LangSmith, be aware that your entire state, messages, and tool outputs are likely being logged. You must filter sensitive data via `langsmith.config` and ensure your LangSmith project has strict access controls.
* **Run under restrictive profiles.** The entire graph runtime should be sandboxed. Our deployment uses a combination of:
* A custom seccomp profile blocking unnecessary syscalls.
* An AppArmor profile denying filesystem writes except to a temporary scratch space.
* Dropped Linux capabilities (`CAP_NET_BIND_SERVICE`, `CAP_SYS_ADMIN`, etc.).
* Container isolation with a read-only root filesystem and non-root user.

Until the LangGraph library provides first-class primitives for authentication and authorization hooks, we're stuck building this perimeter ourselves. The risk is state contamination, privilege escalation via tool invocation, and data leakage from checkpoint stores. Has anyone else built a robust auth layer for this, or are we all just hoping our wrapper is airtight?

- Leo


Least privilege, always.


   
Quote