Data: Serialized graph state as
(type_tag, bytes) tuples. Type tags include
"msgpack", "json", "pickle",
"bytes", "null". When encrypted:
"msgpack+aes", "json+aes".
Validation: Type tag dispatches to codec. Msgpack:
_create_msgpack_ext_hook with allowlist check —
SAFE_MSGPACK_TYPES (47 entries) always checked first, then
allowed_modules determines behavior for unregistered types
(libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:_create_msgpack_ext_hook).
JSON: _reviver with lc:2 module allowlist.
Pickle: no restrictions
(pickle.loads(data_) if pickle_fallback=True,
libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:JsonPlusSerializer.loads_typed).
The proposed secure_pickle.py with
RestrictedUnpickler was documented in
SECURITY_FIX_SUMMARY.md but never merged.
Trust assumption: Checkpoint storage is
access-controlled. An attacker with write access to the database can
craft malicious checkpoint data.
DF3: LLM -> ToolNode
Data: Tool call name and arguments from
LLM-generated AIMessage.tool_calls.
Validation: Tool name checked against registered
tools_by_name dict — unknown names return error
ToolMessage
(libs/prebuilt/langgraph/prebuilt/tool_node.py:ToolNode._validate_tool_call).
Argument values validated only by the target tool’s Pydantic
schema.
Trust assumption: LLM output is treated as
untrusted for tool name routing but argument values pass through to
tools without ToolNode-level sanitization.
DF4: ToolNode ->
User Tools (with Injection)
Data: Parsed argument dicts from LLM, merged with
system-injected InjectedState/InjectedStore/ToolRuntime values.
Validation: Four-layer defense: (1) Injected
parameter names hidden from LLM via tool_call_schema
filtering. (2) Dict merge {**llm_args, **injected_args}
places system values last — system always wins on collision
(libs/prebuilt/langgraph/prebuilt/tool_node.py:ToolNode._inject_tool_args
line 1380). (3) Pydantic model_validate with default
extra="ignore" drops unknown keys. (4) Output construction
only includes declared model fields.
Trust assumption: LLM-provided arguments cannot
override system-injected values due to merge order.
DF5: RemoteGraph -> Pregel
Data: Stream event chunks containing dicts for
Interrupt, Command, state snapshots.
Validation: None on inbound data.
Interrupt(**i) uses dict-splatting with no schema check
(libs/langgraph/langgraph/pregel/remote.py:RemoteGraph.stream).
Command(**chunk.data) uses dict-splatting for parent
commands.
Trust assumption: Remote server is trusted. A
compromised or malicious server can inject arbitrary field values.
Validation: Schema validation in
validate_config_file()
(libs/cli/langgraph_cli/config.py:validate_config_file).
Config values embedded in Dockerfile via json.dumps() in
single-quoted ENV lines
(libs/cli/langgraph_cli/config.py:python_config_to_docker).
Build command content validation
(libs/cli/langgraph_cli/config.py:has_disallowed_build_command_content)
blocks shell metacharacters.
Trust assumption: langgraph.json is
developer-authored. Single quotes in config values could break
Dockerfile ENV syntax.
DF11: Checkpoint Storage
-> BaseCache
Data: Cached task results stored via
BaseCache.set() and retrieved via
BaseCache.get().
Validation: Uses
JsonPlusSerializer(pickle_fallback=False) by default
(libs/checkpoint/langgraph/cache/base/__init__.py:BaseCache).
Subject to same msgpack deserialization behavior as DF1 (allowed_modules
defaults based on LANGGRAPH_STRICT_MSGPACK).
Trust assumption: Cache storage has same access
controls as checkpoint storage.
DF12-13:
Server -> SDK -> Redirect Target (API Key Leak)
Data: Server provides Location header
in HTTP response. SDK follows the redirect and sends all original
request headers (including x-api-key) to the target
URL.
Validation: None on Location URL.
No allowlist, no same-origin check, no header stripping on cross-origin
redirect.
Trust assumption: The LangGraph Server is trusted
to not redirect to malicious URLs. Violated if server is
compromised.
DF14: Pregel
State -> ToolNode (Runtime Injection)
Validation: Injection targets determined by tool
type annotations at compile time. Injected values overwrite any
LLM-provided values with matching keys (safe merge order). Pydantic
validation on tool input drops extra keys not in the tool’s declared
schema.
Trust assumption: System-injected values are
trusted; LLM-provided values cannot interfere due to merge order
guarantees.
DF15: Developer
Code -> SDK Encryption Handlers
Data: Async Python callables registered via
decorators for blob/JSON encryption/decryption and context
derivation.
Validation: _validate_handler checks
async-ness and 2-param signature for encrypt/decrypt handlers.
DuplicateHandlerError prevents double registration.
Gap: Encryption.context() method does NOT
call _validate_handler — a sync function or wrong param
count passes registration and fails only at server-side invocation
(libs/sdk-py/langgraph_sdk/encryption/__init__.py:Encryption.context).
Trust assumption: Handler authors are application
developers with server-level trust.