# LangChain serialization injection vulnerability enables secret extraction

LangChain JS toJSON() failed to escape user data containing 'lc' keys, letting injected secret/constructor structures load env vars or instantiate classes on deserialization.

- Payout: Research
- Severity: high
- Program: LangChain
- Found by: 0xn3va, yardenporat353, VladimirEliTokarev, siewer
- Disclosed: 2025-12-23
- CVE: CVE-2025-68665
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r399-636x-v7f6
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r399-636x-v7f6
- Tags: area:ai domain:ai domain:oss impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:llm tech:nodejs technique:source-review vuln:deserialization vuln:secrets-leak

**Target:** LangChain JS @langchain/core Serializable.toJSON() and load() deserializer
**Root cause:** Serializable.toJSON() did not escape user-controlled objects containing 'lc' keys in kwargs, so on load() they were treated as legitimate LangChain serialized objects rather than plain data.
**Steps:**
1. Serialize untrusted data (e.g. LLM additional_kwargs/response_metadata) via JSON.stringify on a Serializable object.
2. Inject payload `{"lc": 1, "type": "secret", "id": ["OPENAI_API_KEY"]}` into a user-controlled field.
3. Call `load(attackerPayload, { secretsFromEnv: true })`.
4. Deserializer resolves the secret node and returns the env var value in place of user_data.
**How found:** Not stated; advisory credits reporters but gives no reasoning trail.
**Exploitation:** Attacker controls a field that gets serialized then deserialized (e.g. via prompt injection into LLM response metadata). Injecting an lc secret node with id of an env var name causes load() with secretsFromEnv enabled to substitute the environment variable's value into the deserialized object, leaking it. Constructor nodes can likewise instantiate classes present in import maps with attacker params, triggering side effects like network or file operations.
**Escalation:** Chaining prompt injection into LLM response fields with the serialization round-trip turns a data-injection primitive into env-var secret exfiltration or arbitrary class instantiation within import maps.
**Insight:** Any serializer that marks its own objects with a magic key must escape that key in user data, otherwise a serialize-then-deserialize round-trip on trusted output becomes an injection sink; check LLM response metadata fields that flow through such round-trips.

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

**PoC extracting env secret via load()**

```
import { load } from "@langchain/core/load";

// Attacker injects secret structure into user-controlled data
const attackerPayload = JSON.stringify({
  user_data: {
    lc: 1,
    type: "secret",
    id: ["OPENAI_API_KEY"],
  },
});

process.env.OPENAI_API_KEY = "sk-secret-key-12345";

// With secretsFromEnv: true, the secret is extracted
const deserialized = await load(attackerPayload, { secretsFromEnv: true });

console.log(deserialized.user_data); // "sk-secret-key-12345" - SECRET LEAKED!
```

