diff --git a/libs/checkpoint-postgres/README.md b/libs/checkpoint-postgres/README.md index c36a60c60..320ecf839 100644 --- a/libs/checkpoint-postgres/README.md +++ b/libs/checkpoint-postgres/README.md @@ -6,6 +6,11 @@ Implementation of LangGraph CheckpointSaver that uses Postgres. By default `langgraph-checkpoint-postgres` installs `psycopg` (Psycopg 3) without any extras. However, you can choose a specific installation that best suits your needs [here](https://www.psycopg.org/psycopg3/docs/basic/install.html) (for example, `psycopg[binary]`). +## Security + +> [!IMPORTANT] +> Set `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list when creating your checkpointer. This restricts checkpoint deserialization to known-safe types, preventing code execution if the database is compromised. See the [langgraph-checkpoint README](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint#serde) for details. + ## Usage > [!IMPORTANT] diff --git a/libs/checkpoint-sqlite/README.md b/libs/checkpoint-sqlite/README.md index 48f4e6690..f38f0c311 100644 --- a/libs/checkpoint-sqlite/README.md +++ b/libs/checkpoint-sqlite/README.md @@ -2,6 +2,11 @@ Implementation of LangGraph CheckpointSaver that uses SQLite DB (both sync and async, via `aiosqlite`) +## Security + +> [!IMPORTANT] +> Set `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list when creating your checkpointer. This restricts checkpoint deserialization to known-safe types, preventing code execution if the database is compromised. See the [langgraph-checkpoint README](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint#serde) for details. + ## Usage ```python diff --git a/libs/checkpoint/README.md b/libs/checkpoint/README.md index 4152c3824..3bfcbccaf 100644 --- a/libs/checkpoint/README.md +++ b/libs/checkpoint/README.md @@ -26,6 +26,9 @@ You must pass these when invoking the graph as part of the configurable part of `langgraph_checkpoint` also defines protocol for serialization/deserialization (serde) and provides an default implementation (`langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer`) that handles a wide variety of types, including LangChain and LangGraph primitives, datetimes, enums and more. +> [!IMPORTANT] +> **Checkpoint deserialization security:** By default the serializer allows any Python type found in checkpoint data. New applications should set the environment variable `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list to `JsonPlusSerializer` to restrict deserialization to known-safe types. + ### Pending writes When a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes. diff --git a/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py b/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py index 0b09a9a4e..35ac48665 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py @@ -1,3 +1,10 @@ +"""Msgpack deserialization safety controls. + +Set ``LANGGRAPH_STRICT_MSGPACK=true`` to restrict checkpoint deserialization +to the types listed in ``SAFE_MSGPACK_TYPES``. Without this, any Python +callable stored in checkpoint data will be imported and executed on load. +""" + import os from collections.abc import Iterable from typing import cast diff --git a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py index 4ef4a40b2..4ee654df8 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py @@ -56,6 +56,10 @@ class JsonPlusSerializer(SerializerProtocol): class and called within the Pregel loop. It should not be used on untrusted python objects. If an attacker can write directly to your checkpoint database, they may be able to trigger code execution when data is deserialized. + + Set the environment variable ``LANGGRAPH_STRICT_MSGPACK=true`` to restrict + deserialization to a built-in allowlist of safe types. You can also pass + an explicit ``allowed_msgpack_modules`` to the constructor. """ def __init__( @@ -70,8 +74,11 @@ class JsonPlusSerializer(SerializerProtocol): ) -> None: if allowed_msgpack_modules is _lg_msgpack._SENTINEL: if _lg_msgpack.STRICT_MSGPACK_ENABLED: + # Strict: only SAFE_MSGPACK_TYPES are allowed. allowed_msgpack_modules = None else: + # Permissive (default): all types allowed with a warning. + # Set LANGGRAPH_STRICT_MSGPACK=true to lock this down. allowed_msgpack_modules = True self.pickle_fallback = pickle_fallback self._allowed_json_modules: set[tuple[str, ...]] | Literal[True] | None = ( @@ -530,7 +537,8 @@ def _create_msgpack_ext_hook( logger.warning( "Deserializing unregistered type %s.%s from checkpoint. " "This will be blocked in a future version. " - "Add to allowed_msgpack_modules to silence: [(%r, %r)]", + "Set LANGGRAPH_STRICT_MSGPACK=true to block now, or add " + "to allowed_msgpack_modules to allow explicitly: [(%r, %r)]", module, name, module,