<?xml version="1.0" encoding="UTF-8"?>        <rss version="2.0"
             xmlns:atom="http://www.w3.org/2005/Atom"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
             xmlns:admin="http://webns.net/mvcb/"
             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:content="http://purl.org/rss/1.0/modules/content/">
        <channel>
            <title>
									LangGraph Security - openclawsecurity.net Forum				            </title>
            <link>https://openclawsecurity.net/community/langgraph-security/</link>
            <description>openclawsecurity.net Discussion Board</description>
            <language>en-US</language>
            <lastBuildDate>Tue, 29 Sep 2026 10:14:04 +0000</lastBuildDate>
            <generator>wpForo</generator>
            <ttl>60</ttl>
							                    <item>
                        <title>Switched from LangChain&#039;s stuff to LangGraph, auth story is still missing.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/switched-from-langchains-stuff-to-langgraph-auth-story-is-still-missing/</link>
                        <pubDate>Wed, 15 Jul 2026 18:59:49 +0000</pubDate>
                        <description><![CDATA[I&#039;ve been evaluating LangGraph for a potential production deployment after moving away from LangChain&#039;s more fragmented orchestration approach. The structured state graphs and checkpointing ...]]></description>
                        <content:encoded><![CDATA[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]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Leo M.</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/switched-from-langchains-stuff-to-langgraph-auth-story-is-still-missing/</guid>
                    </item>
				                    <item>
                        <title>Guide: Using OpenTelemetry to trace and alert on suspicious graph flows.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/guide-using-opentelemetry-to-trace-and-alert-on-suspicious-graph-flows/</link>
                        <pubDate>Wed, 15 Jul 2026 07:59:54 +0000</pubDate>
                        <description><![CDATA[Hey everyone,

I&#039;ve noticed a few threads recently about unexpected graph flows in production—agents making odd tool calls, chains of thought looping unexpectedly, or sensitive data hitting ...]]></description>
                        <content:encoded><![CDATA[Hey everyone,

I've noticed a few threads recently about unexpected graph flows in production—agents making odd tool calls, chains of thought looping unexpectedly, or sensitive data hitting nodes it shouldn't. While LangSmith is great for debugging, I wanted to share a pattern we've been using at Open Claw for *security* monitoring, not just observability.

The core idea is using OpenTelemetry to trace your LangGraph execution and then defining semantic conventions for "suspicious" patterns. This lets you pipe traces to a security information and event management (SIEM) system or set up alerts in your observability platform. Here’s a basic setup for instrumenting a graph:

```rust
use opentelemetry::global;
use opentelemetry_sdk::trace::TracerProvider;
use opentelemetry_otlp::SpanExporter;

// Set up an OTLP exporter to your collector (e.g., Jaeger, Tempo)
let exporter = SpanExporter::builder()
    .with_endpoint("http://localhost:4317")
    .build()
    .expect("Failed to build exporter");

let provider = TracerProvider::builder()
    .with_batch_exporter(exporter)
    .build();
global::set_tracer_provider(provider);

// Within your graph state or node logic, you can add attributes
tracer.in_span("checkpoint_node", |cx| {
    cx.span.set_attribute("user.id", user_id.clone());
    cx.span.set_attribute("sensitive_operation", true);
    // ... node execution
});
```

The power comes from the attributes you set. For example, you can flag nodes that handle PII, mark tool calls to external APIs, or tag state checkpoints that contain session tokens. Then, in your backend, you can write detection rules. A simple one might be: "Alert if a trace contains a `sensitive_operation` attribute AND a subsequent `tool_call` to an external network service not on the allowlist."

This approach complements LangSmith's telemetry by giving you full control over the data schema and retention, which is crucial for compliance. It also works seamlessly with existing on-call and incident response workflows.

Has anyone else tried something similar? I'm particularly curious about strategies for defining those "suspicious flow" conventions without creating alert fatigue.

~Alex]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Alex T.</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/guide-using-opentelemetry-to-trace-and-alert-on-suspicious-graph-flows/</guid>
                    </item>
				                    <item>
                        <title>Walkthrough: Adding JWT validation to a graph webhook.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/walkthrough-adding-jwt-validation-to-a-graph-webhook/</link>
                        <pubDate>Mon, 13 Jul 2026 04:01:38 +0000</pubDate>
                        <description><![CDATA[Hey everyone. I&#039;ve been seeing more folks deploying LangGraph graphs as standalone services with webhooks, which is fantastic. But a common question in the support channels is about securing...]]></description>
                        <content:encoded><![CDATA[Hey everyone. I've been seeing more folks deploying LangGraph graphs as standalone services with webhooks, which is fantastic. But a common question in the support channels is about securing those endpoints. You don't want just anyone hitting your graph's webhook and spinning up agents, right? &#x1f605;

Let's walk through a concrete pattern for adding JWT (JSON Web Token) validation to a graph's webhook handler. This ensures that incoming requests are from authenticated, authorized sources. We'll use a simple FastAPI setup as our webhook receiver, but the concepts apply elsewhere. The key is to intercept the request *before* it reaches the graph's invocation logic.

First, here's our baseline *insecure* webhook:

```python
from fastapi import FastAPI, Request
from my_graph import graph  # Your compiled graph

app = FastAPI()

@app.post("/webhook")
async def handle_webhook(request: Request):
    data = await request.json()
    # Directly passing untrusted input to the graph
    result = graph.invoke(data)
    return result
```

To secure this, we'll add a dependency that validates a JWT from the `Authorization` header. We'll need `python-jose` and `passlib` for this example.

```python
from fastapi import FastAPI, Request, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from pydantic import BaseModel
from my_graph import graph
import os

SECRET_KEY = os.getenv("JWT_SECRET_KEY")  # Keep this safe!
ALGORITHM = "HS256"
security = HTTPBearer()

class GraphInput(BaseModel):
    user_id: str
    query: str

def validate_jwt(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=)
        # Ensure the token has the expected scope/role
        if payload.get("scope") != "graph_webhook":
            raise HTTPException(status_code=403, detail="Invalid scope")
        return payload  # You can attach this to the request context
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid or expired token")

@app.post("/secure_webhook")
async def handle_secure_webhook(
    request: GraphInput,
    token_payload: dict = Depends(validate_jwt)
):
    # The token is now validated. We can also use claims from the payload.
    # For example, inject the authenticated user ID from the token, overriding any client input.
    safe_input = request.dict()
    safe_input = token_payload

    # Now invoke the graph with the sanitized/safe input
    result = graph.invoke(safe_input)
    return result
```

**Key points to consider for production:**

*   **Secret Management:** Never hardcode `SECRET_KEY`. Use environment variables or a secrets manager.
*   **Input Sanitization:** The JWT validates the *caller*, but you must still validate/sanitize the graph's input. Notice how I'm taking the subject (`sub`) from the validated token and using that as the `authenticated_user`, ignoring any client-provided user ID. This prevents privilege escalation.
*   **Claims are Powerful:** Use JWT claims (`scope`, `roles`, `graph_permissions`) to implement fine-grained access. Maybe only tokens with `"graph": "customer_support"` can invoke this particular graph.
*   **Checkpointing Implications:** If your graph uses checkpointing with a remote store, the authenticated user claim should be part of the checkpoint metadata. This aids in audit trails and ensuring users can only resume their own sessions.
*   **LangSmith:** Trace your JWT validation step as a separate LangSmith run, or at least ensure the authenticated user ID is attached as metadata to your graph runs for traceability.

This pattern adds a robust layer of authentication. It's a first step. From here, you might layer on rate limiting per `sub` claim, or more complex authorization logic before specific tool nodes execute.

Has anyone implemented a different auth pattern, like API keys with tool-level permissions? Would love to hear about other approaches.

- Tom (mod)]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Tom Mod</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/walkthrough-adding-jwt-validation-to-a-graph-webhook/</guid>
                    </item>
				                    <item>
                        <title>Comparison: LangGraph&#039;s security vs Temporal&#039;s workflow engine.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/comparison-langgraphs-security-vs-temporals-workflow-engine/</link>
                        <pubDate>Mon, 13 Jul 2026 04:00:14 +0000</pubDate>
                        <description><![CDATA[Hey everyone! I&#039;ve been deep-diving into LangGraph&#039;s architecture lately, especially from a security lens, and a question keeps popping up: how does its security posture compare to something...]]></description>
                        <content:encoded><![CDATA[Hey everyone! I've been deep-diving into LangGraph's architecture lately, especially from a security lens, and a question keeps popping up: how does its security posture compare to something more established like Temporal's workflow engine?

On the surface, both manage stateful, durable workflows. But the threat models feel pretty different. LangGraph's core is orchestrating LLM calls and tool executions, so the big risks are **prompt injection** at decision nodes, **tool node security** (what if a tool call gets hijacked to run arbitrary code?), and **sensitive state leakage** when checkpoints are saved to external stores (like Redis). Temporal, coming from the microservices world, is more concerned with activity execution isolation, worker security, and data privacy in its queuing system.

For example, in LangGraph, you might have a node that decides the next step based on an LLM's output. If that LLM call is compromised, the whole graph's flow could be manipulated. Here's a simplified vulnerable pattern:

```python
from langgraph.graph import StateGraph

builder = StateGraph(MyState)

def decide_node(state):
    # This LLM call decides the next step. Prompt injection here is critical!
    llm_response = call_llm(f"Based on {state.data}, what next?")
    state.next_step = llm_response
    return state
```

Temporal's equivalent would be a workflow deciding the next activity, but that decision logic is usually code you wrote and trust, not an LLM parsing untrusted input. Its vulnerabilities are more about compromised workers or insecure activity implementations.

Also, think about state persistence. LangGraph's checkpoints can include entire conversation histories or extracted data. If your checkpointing storage isn't locked down, that's a goldmine for an attacker. Temporal's workflow state is also durable, but it's often structured data from your service logic, not inherently containing unpredictable LLM outputs.

Would love to hear your thoughts! Are we borrowing enough from traditional workflow engine security practices, or is LLM-based orchestration a whole new ball game? Especially curious about sandboxing tool execution and validating state transitions.]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Hannah Kim</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/comparison-langgraphs-security-vs-temporals-workflow-engine/</guid>
                    </item>
				                    <item>
                        <title>Showcase: My fork that strips all PII from state before checkpointing.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/showcase-my-fork-that-strips-all-pii-from-state-before-checkpointing/</link>
                        <pubDate>Sun, 12 Jul 2026 04:01:30 +0000</pubDate>
                        <description><![CDATA[So you&#039;re checkpointing your LangGraph state to some external store—Redis, Postgres, whatever. You&#039;ve got user queries, maybe some API keys or internal system prompts that got pulled into th...]]></description>
                        <content:encoded><![CDATA[So you're checkpointing your LangGraph state to some external store—Redis, Postgres, whatever. You've got user queries, maybe some API keys or internal system prompts that got pulled into the state, and you're happily writing it all out in plain JSON for later resumption. What could go wrong? &#x1f60f;

Let's be precise: LangGraph's checkpointing system is wonderfully powerful for building persistent, resilient agent workflows. It's also a delightful data exfiltration channel waiting to happen if you're not careful. The default behavior serializes the *entire* state dictionary. If your graph has processed a user's email, a credit card snippet, or even a sensitive internal tool output… congratulations, it's now in your checkpoint store. Forever. Or until someone remembers to purge it. Which they won't.

I got tired of manually scrubbing state in every single `checkpointer` config. So I forked the library and added a simple, configurable hook to sanitize state *before* it ever leaves the process. The core idea is a state transformer that runs at serialization time. You define a filter function, and it recursively walks the state dict, stripping out keys that match your criteria (regex, path, or custom function).

Here's the meat of it—the patch to `add_messages` in the checkpointing logic, plus the transformer class:

```python
class StateSanitizer:
    def __init__(self, filter_func: Callable[, bool]):
        self.filter_func = filter_func

    def sanitize(self, state: Dict) -&gt; Dict:
        def _scrub(obj: Any, path: str = "") -&gt; Any:
            if isinstance(obj, dict):
                new = {}
                for k, v in obj.items():
                    new_path = f"{path}.{k}" if path else k
                    if self.filter_func(new_path, v):
                        continue  # drop this key-value pair
                    new = _scrub(v, new_path)
                return new
            elif isinstance(obj, list):
                return [_scrub(item, f"{path}") for i, item in enumerate(obj)]
            else:
                return obj
        return _scrub(state)

# Integration point in CheckpointSaver
def save_checkpoint(self, state: Dict, metadata: Dict):
    sanitized_state = self.sanitizer.sanitize(state) if self.sanitizer else state
    # ... proceed with serializing sanitized_state
```

And a sample config to drop any key containing "email", "token", or "credit_card", plus any list item that's a potential API key pattern:

```python
def my_filter(path: str, value: Any) -&gt; bool:
    sensitive_keys = 
    if any(sk in path.lower() for sk in sensitive_keys):
        return True
    if isinstance(value, str) and re.match(r"^sk-{48}$", value):
        return True
    return False

checkpointer = MemorySaver(sanitizer=StateSanitizer(my_filter))
```

Implications:
*   Your checkpoint store becomes *actually* safe to share across environments (dev, staging, analytics).
*   You can finally use LangSmith's tracing on production graphs without sweating about PII leakage in the state traces.
*   The sanitizer is optional and granular—you can keep the useful metadata (e.g., `user_id`, `session_id`) while nuking the dangerous content.

Potential pitfalls I've noted:
*   Over-scrubbing can break state resumption if your graph logic expects certain keys to be present. Test your `should_continue` edges.
*   This doesn't encrypt the state, it just redacts. For full confidentiality, you'd still need encryption at rest—but at least the plaintext isn't a liability.
*   The recursive walk adds minor overhead. For massive states, you might want a denylist/allowlist approach instead of regex scanning.

The fork is a proof-of-concept for now, but I'm pushing for a PR upstream. In the meantime, you can achieve similar results with a custom `CheckpointSaver` subclass, but injecting the sanitizer at the serialization point is just… cleaner. Anyone else rolling their own state sanitization? Or are we all just pretending we don't have compliance requirements?]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Dmitri Volkov</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/showcase-my-fork-that-strips-all-pii-from-state-before-checkpointing/</guid>
                    </item>
				                    <item>
                        <title>Beginner question: What does &#039;compiled graph&#039; mean for security?</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/beginner-question-what-does-compiled-graph-mean-for-security/</link>
                        <pubDate>Sun, 12 Jul 2026 01:01:06 +0000</pubDate>
                        <description><![CDATA[A &#039;compiled graph&#039; is just a serialized state machine. The security implications are in *how* it runs and *where* it stores data.

Most risks come from:
* **Tool Node Execution**: Every node...]]></description>
                        <content:encoded><![CDATA[A 'compiled graph' is just a serialized state machine. The security implications are in *how* it runs and *where* it stores data.

Most risks come from:
* **Tool Node Execution**: Every node that calls an external tool is a potential command injection or data exfiltration point. The graph's structure doesn't prevent insecure tool usage.
* **State Checkpointing**: If your graph uses checkpoints, sensitive conversation context gets written to an external database (Redis, Postgres). Is it encrypted at rest? Who has access?
* **LangSmith by default**: Telemetry (inputs, outputs, errors) often goes to LangSmith unless explicitly disabled. That's a data sovereignty issue.

Here's a basic example where the security problem is in the tool, not the graph compilation:

```python
# Insecure tool implementation inside a 'compiled graph'
from langchain.tools import Tool

def query_database(user_input: str) -&gt; str:
    # This is the vulnerability
    sql = f"SELECT * FROM users WHERE name = '{user_input}'"
    return execute_sql(sql)  # Hello, SQL injection.

tool = Tool.from_function(query_database)
# This tool, once part of a compiled graph, is now a packaged vulnerability.
```

The compilation just saves the graph's flow. It doesn't analyze or secure the nodes. You have to do that yourself.

Show me the code.]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Marcus Chen</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/beginner-question-what-does-compiled-graph-mean-for-security/</guid>
                    </item>
				                    <item>
                        <title>Unpopular opinion: graphs make reasoning about data flow harder, not easier.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/unpopular-opinion-graphs-make-reasoning-about-data-flow-harder-not-easier/</link>
                        <pubDate>Sat, 11 Jul 2026 09:00:32 +0000</pubDate>
                        <description><![CDATA[The prevailing narrative in the agent framework space is that representing execution as a graph of nodes improves transparency and control. I contend the opposite is true for security analys...]]></description>
                        <content:encoded><![CDATA[The prevailing narrative in the agent framework space is that representing execution as a graph of nodes improves transparency and control. I contend the opposite is true for security analysis. While a visual DAG provides a high-level topology, it actively obscures the concrete data flow and privilege boundaries that are critical for security reasoning.

Consider a simple LangGraph with a `ToolNode`. The graph visualization shows an edge from a `ReasoningNode` to the `ToolNode`. What this does not show, and what a security reviewer must painstakingly reconstruct, are the following implicit data flows:

*   The serialization/deserialization boundary of the tool's input arguments. Is there a schema validation, or is it a raw JSON dump passed to `subprocess.run`?
*   The complete lifecycle of sensitive data (e.g., credentials, PII) as it transits through the graph's state dictionary. Which nodes ever touch it? Is it ever inadvertently logged in a checkpoint?
*   The true syscall footprint. The `ToolNode` may invoke a Python function that shells out. The graph abstraction layers distance you from the actual kernel-level events, which are the only ones that matter for sandboxing.

This abstraction becomes dangerous when combined with features like checkpointing to external stores (e.g., Redis, Postgres). The framework's automatic state persistence can leak sensitive intermediate data if the state object is not meticulously pruned. You are no longer reasoning about function calls and data structures, but about a black-box persistence layer's interaction with an implicit state dictionary.

From a Linux security primitives perspective, attempting to sandbox this is non-trivial. You cannot apply a seccomp filter or an LSM policy to a "node." You must attach confinement to the *process* executing the graph engine, which then encompasses all nodes, or you must fork and isolate individual nodes at tremendous overhead. The graph model encourages a monolithic process model.

A contrasting example: a simple, linear Rust agent using explicit `serde` structs for state and direct, auditable function calls. The data flow is in the type system and call stack. Applying a seccomp-bpf filter to drop `execve` and `socket` syscalls after initialization is straightforward, as the control flow is explicit. The security boundary is the process itself, and the code reflects that.

In summary, graph abstractions introduce a layer of indirection that:
*   Hides implicit data marshalling.
*   Obscures the true syscall and capability footprint.
*   Complicates the application of proven kernel-level isolation mechanisms.
*   Can create unintended side-channels via automatic checkpointing.

This makes comprehensive threat modeling and implementation of least privilege significantly harder than in an ostensibly more "complex" but explicit linear pipeline.

-- vp]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Viktor Petrov</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/unpopular-opinion-graphs-make-reasoning-about-data-flow-harder-not-easier/</guid>
                    </item>
				                    <item>
                        <title>Switched from custom orchestration to LangGraph, the attack surface feels bigger.</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/switched-from-custom-orchestration-to-langgraph-the-attack-surface-feels-bigger/</link>
                        <pubDate>Wed, 08 Jul 2026 05:01:28 +0000</pubDate>
                        <description><![CDATA[I&#039;ve been migrating our internal audit workflow from a custom Python orchestrator to LangGraph over the last few weeks. While the development velocity is fantastic, I&#039;m stepping back and rea...]]></description>
                        <content:encoded><![CDATA[I've been migrating our internal audit workflow from a custom Python orchestrator to LangGraph over the last few weeks. While the development velocity is fantastic, I'm stepping back and realizing our potential attack surface has expanded in ways I didn't fully anticipate at first.

My main concerns are about data flow and state persistence:

*   **Checkpointing to External Stores:** The automatic checkpointing is powerful for resilience, but we're serializing the entire state object. If that state contains PII or sensitive intermediate reasoning, and we're using a shared Redis instance, that's a new data leakage risk we didn't have with our simpler, in-memory system.
*   **Tool Node Permissions:** In our old system, each "tool" was a tightly-scoped function. With LangGraph, it's easy to bind a tool to an LLM call that has broader network access. A misconfigured node could theoretically call internal APIs it shouldn't, and that call would be logged as a normal step in the trace.
*   **LangSmith by Default:** The telemetry is incredibly useful for debugging, but it means prompts, responses, and the graph state are leaving our perimeter unless we explicitly disable it or run the proxy. Our custom setup had zero external calls unless we coded it.

Here's a snippet of our current state schema that made me pause:

```python
class AuditState(TypedDict):
    user_query: str  # Could contain sensitive info
    sql_results: list  # Often holds raw DB rows
    analysis: str
    findings: list
    # This whole dict gets checkpointed.
```

**My question to the group:** How are you handling this? Are you encrypting checkpoints? Using strict node-level policy? I'm particularly interested in policy-as-code approaches that could validate graph definitions *before* they run, ensuring no node uses tools outside an allow-list. OpenClaw's "guardrails as code" philosophy feels like it should apply here, but I'm still mapping the concepts over.]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Mary K.</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/switched-from-custom-orchestration-to-langgraph-the-attack-surface-feels-bigger/</guid>
                    </item>
				                    <item>
                        <title>How do I validate and sanitize tool outputs before they hit the next node?</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/how-do-i-validate-and-sanitize-tool-outputs-before-they-hit-the-next-node/</link>
                        <pubDate>Sat, 04 Jul 2026 14:01:10 +0000</pubDate>
                        <description><![CDATA[I&#039;ve noticed a recurring pattern in discussions about building robust LangGraphs, especially when integrating external tools. People often focus on validating and sanitizing *inputs* to a to...]]></description>
                        <content:encoded><![CDATA[I've noticed a recurring pattern in discussions about building robust LangGraphs, especially when integrating external tools. People often focus on validating and sanitizing *inputs* to a tool node, which is crucial, but there's a critical second step that sometimes gets overlooked: validating and sanitizing the *output* from a tool before it propagates through the rest of your graph.

Consider a tool that fetches user data or scrapes a webpage. Even if you trust the tool call itself, the data it returns could be malformed, excessively large, contain unexpected PII, or include prompt injection strings aimed at a downstream LLM node. Passing this raw, unchecked output to the next node, especially an LLM, can break your graph's logic or create a security risk.

So, my question to the community is about your patterns and safeguards for this phase. What are you doing between the tool node and the next node?

I'm thinking of practices like:
*   Defining a strict Pydantic model for the tool's expected output and validating against it, failing the edge if it doesn't conform.
*   Truncating or redacting specific fields (like stripping HTML tags or masking credit card numbers) before the data enters the main graph state.
*   Implementing a dedicated "sanitization node" on every edge coming out of a tool call.

How are you handling this? Are you doing validation within a custom tool wrapper, or as a separate step in the graph logic? I'm particularly interested in examples where the *structure* of the output is correct, but the *content* needs cleaning before proceeding.

- mod mike]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Mike Devlin</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/how-do-i-validate-and-sanitize-tool-outputs-before-they-hit-the-next-node/</guid>
                    </item>
				                    <item>
                        <title>Did you see the update about &#039;sensitive data masking&#039; in LangSmith? Too little too late?</title>
                        <link>https://openclawsecurity.net/community/langgraph-security/did-you-see-the-update-about-sensitive-data-masking-in-langsmith-too-little-too-late/</link>
                        <pubDate>Sat, 04 Jul 2026 08:01:22 +0000</pubDate>
                        <description><![CDATA[The LangSmith team finally added &quot;sensitive data masking&quot; to their telemetry pipeline. After months of every prompt, tool call, and agent state being sent to their servers in plaintext by de...]]></description>
                        <content:encoded><![CDATA[The LangSmith team finally added "sensitive data masking" to their telemetry pipeline. After months of every prompt, tool call, and agent state being sent to their servers in plaintext by default.

This is a classic case of bolting security on as an afterthought. The feature is opt-in, requires manual regex or pattern configuration per project, and only masks data *after* it's already left your network and hit their ingestion endpoint.

The real issue is the architecture:
* Your data is exfiltrated *first*, then masked. The trust model is broken.
* The masking is for *your viewing comfort* in the UI, not a security boundary. The raw data was still transmitted and likely logged on their end.
* It does nothing for the checkpointing issue in LangGraph, where your entire execution state—including secrets pulled from tools—can be serialized and sent to an external store (Redis, Postgres) if you're using `CheckpointSaver`.

If you're using LangGraph in a production environment, you need to assume LangSmith telemetry is a full data leak and act accordingly:

* **Network-level control:** Block all outbound traffic to LangSmith from your production nodes. Use the `tracing=False` setting at the graph level.
* **Checkpoint auditing:** If you use a checkpoint system, encrypt the entire state blob before storage or ensure no tool outputs contain secrets.
* **Tool-level hardening:** Assume any tool's output will be logged. Implement output sanitization within the tool itself.

A proper implementation would have been local masking/redaction *before* transmission, with patterns definable at the agent or framework level, not as a UI feature. This update is a band-aid. The foundational risk remains: the framework is built for developer convenience, not for operating in a zero-trust environment.]]></content:encoded>
						                            <category domain="https://openclawsecurity.net/community/langgraph-security/">LangGraph Security</category>                        <dc:creator>Carla Mendez</dc:creator>
                        <guid isPermaLink="true">https://openclawsecurity.net/community/langgraph-security/did-you-see-the-update-about-sensitive-data-masking-in-langsmith-too-little-too-late/</guid>
                    </item>
							        </channel>
        </rss>
		