18 known bugs in langchain_core, with affected versions, fixes and workarounds. Sourced from upstream issue trackers.
| Severity | Affected | Fixed in | Title | Status | Source |
|---|
| high | any | 1.2.22 | LangChain Core has Path Traversal vulnerabilites in legacy `load_prompt` functions ## Summary
Multiple functions in `langchain_core.prompts.loading` read files from paths embedded in deserialized config dicts without validating against directory traversal or absolute path injection. When an application passes user-influenced prompt configurations to `load_prompt()` or `load_prompt_from_config()`, an attacker can read arbitrary files on the host filesystem, constrained only by file-extension checks (`.txt` for templates, `.json`/`.yaml` for examples).
**Note:** The affected functions (`load_prompt`, `load_prompt_from_config`, and the `.save()` method on prompt classes) are undocumented legacy APIs. They are superseded by the `dumpd`/`dumps`/`load`/`loads` serialization APIs in `langchain_core.load`, which do not perform filesystem reads and use an allowlist-based security model. As part of this fix, the legacy APIs have been formally deprecated and will be removed in 2.0.0.
## Affected component
**Package:** `langchain-core`
**File:** `langchain_core/prompts/loading.py`
**Affected functions:** `_load_template()`, `_load_examples()`, `_load_few_shot_prompt()`
## Severity
**High**
The score reflects the file-extension constraints that limit which files can be read.
## Vulnerable code paths
| Config key | Loaded by | Readable extensions |
|---|---|---|
| `template_path`, `suffix_path`, `prefix_path` | `_load_template()` | `.txt` |
| `examples` (when string) | `_load_examples()` | `.json`, `.yaml`, `.yml` |
| `example_prompt_path` | `_load_few_shot_prompt()` | `.json`, `.yaml`, `.yml` |
None of these code paths validated the supplied path against absolute path injection or `..` traversal sequences before reading from disk.
## Impact
An attacker who controls or influences the prompt configuration dict can read files outside the intended directory:
- **`.txt` files:** cloud-mounted secrets (`/mnt/secrets/api_key.txt`), `requirements.txt`, internal system prompts
- **`.json`/`.yaml` files:** cloud credentials (`~/.docker/config.json`, `~/.azure/accessTokens.json`), Kubernetes manifests, CI/CD configs, application settings
This is exploitable in applications that accept prompt configs from untrusted sources, including low-code AI builders and API wrappers that expose `load_prompt_from_config()`.
## Proof of concept
```python
from langchain_core.prompts.loading import load_prompt_from_config
# Reads /tmp/secret.txt via absolute path injection
config = {
"_type": "prompt",
"template_path": "/tmp/secret.txt",
"input_variables": [],
}
prompt = load_prompt_from_config(config)
print(prompt.template) # file contents disclosed
# Reads ../../etc/secret.txt via directory traversal
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
prompt = load_prompt_from_config(config)
# Reads arbitrary .json via few-shot examples
config = {
"_type": "few_shot",
"examples": "../../../../.docker/config.json",
"example_prompt": {
"_type": "prompt",
"input_variables": ["input", "output"],
"template": "{input}: {output}",
},
"prefix": "",
"suffix": "{query}",
"input_variables": ["query"],
}
prompt = load_prompt_from_config(config)
```
## Mitigation
**Update `langchain-core` to >= 1.2.22.**
The fix adds path validation that rejects absolute paths and `..` traversal sequences by default. An `allow_dangerous_paths=True` keyword argument is available on `load_prompt()` and `load_prompt_from_config()` for trusted inputs.
As described above, these legacy APIs have been formally deprecated. Users should migrate to `dumpd`/`dumps`/`load`/`loads` from `langchain_core.load`.
## Credit
- [jiayuqi7813](https://github.com/jiayuqi7813) reporter
- [VladimirEliTokarev](https://github.com/VladimirEliTokarev) reporter
- [Rickidevs](https://github.com/Rickidevs) reporter
- Kenneth Cox ([email protected]) reporter | fixed | osv:GHSA-qh6h-p6c9-ff54 |
| high | 1.0.0 | 1.3.3 | LangChain vulnerable to unsafe deserialization of attacker-controlled objects through overly broad `load()` allowlists LangChain contains older runtime code paths that deserialize run inputs, run outputs, or other application-controlled payloads using overly broad object allowlists. These paths may call `load()` with `allowed_objects="all"`. This does not enable arbitrary Python object deserialization, but it does allow any trusted LangChain-serializable object to be revived, which is broader than these runtime paths require. As a result, attacker-supplied LangChain serialized constructor dictionaries may cause trusted runtime paths to instantiate classes with untrusted constructor arguments.
Applications are exposed only when all of the following are true:
1. The application accepts untrusted structured input, such as JSON, from a user or network request.
2. The application does not validate or canonicalize that input into an inert schema before invoking LangChain.
3. Attacker-controlled nested dictionaries or lists are preserved in LangChain run inputs or outputs.
4. The application uses an affected API path that later deserializes that run data.
Known affected runtime surfaces include:
- `RunnableWithMessageHistory`
- `astream_log()`
- `astream_events(version="v1")`
Related unsafe deserialization patterns may also affect applications that explicitly load serialized LangChain prompt or runnable objects from untrusted sources, including shared prompt stores, Hub artifacts with model configuration, or other application-controlled serialization stores.
Applications that validate incoming requests against a fixed schema, such as coercing user input to a plain string or message-content field before invoking LangChain, are unlikely to expose this deserialization primitive.
This release also fixes a related secret-marker validation bypass in the serialization and deserialization layer (`_is_lc_secret`). That issue creates an additional path by which attacker-controlled constructor dictionaries can avoid escaping during `dumps()` -> `loads()` round-trips and reach LangChain object revival logic.
## Impact
An attacker who can submit untrusted structured input to an affected application, and have that structure preserved in LangChain run data, may be able to inject LangChain serialized constructor payloads such as:
```json
{
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "ai", "AIMessage"],
"kwargs": {"content": "attacker-controlled content"}
}
```
If this payload reaches a broad `load()` call, LangChain may instantiate the referenced class instead of treating the payload as inert user data.
Realistic impacts include:
- Persistent chat-history poisoning when revived `AIMessage`, `HumanMessage`, or `SystemMessage` objects are stored by `RunnableWithMessageHistory`.
- Prompt injection or behavior manipulation if attacker-controlled messages are later included in model context.
- Instantiation of unexpected trusted LangChain objects with attacker-controlled constructor arguments.
- Possible credential disclosure or server-side requests if a reachable object reads environment credentials, creates clients, or contacts attacker-controlled endpoints during initialization.
- Additional prompt-template or runnable-configuration impacts in applications that separately load and execute untrusted serialized LangChain objects.
## Remediation
LangChain will deprecate the affected APIs as part of this fix:
- `RunnableWithMessageHistory`
- `astream_log()`
- `astream_events(version="v1")`
These are older code paths that are no longer recommended for new applications. They were not previously marked as deprecated, but recent LangChain documentation has primarily directed users toward newer streaming and memory patterns, including the `stream` API. Applications should migrate to the currently recommended APIs rather than continue depending on these older surfaces.
Separately, LangChain will update `load()` and `loads()` to tighten deserialization behavior so broad object revival is not applied implicitly to untrusted or application-controlled payloads. The older runtime surfaces listed above are being deprecated rather than preserved as supported paths for broad runtime deserialization.
This release also fixes a related secret-marker validation bypass in the serialization and deserialization layer (`_is_lc_secret`). That issue creates an additional path by which attacker-controlled constructor dictionaries can avoid escaping during `dumps()` -> `loads()` round-trips and reach LangChain object revival logic.
## Guidance for `load()` and `loads()`
`load()` and `loads()` should be used only with trusted LangChain manifests or serialized objects from trusted storage. Do not pass user-controlled data to `load()` or `loads()`, and do not use them as general parsers for request bodies, tool inputs, chat messages, or other attacker-controlled data.
`load()` and `loads()` are beta APIs, and their behavior may change as LangChain narrows unsafe defaults. Future LangChain versions will require callers to be explicit about which objects may be revived. Users should pass a narrow `allowed_objects` value appropriate for the specific trusted manifest they are loading, rather than relying on broad defaults or `allowed_objects="all"`, which permits the full trusted LangChain serialization allowlist.
## Credits
The original issue was first reported by @u-ktdi.
Similar findings were reported by @dewankpant, @shrutilohani, @Moaaz-0x, @pucagit.
A related `_is_lc_secret` marker bypass affecting `dumps()` -> `loads()` round-trips was reported by @yardenporat353 (and a similar report by @localhost-detect) | ||
| high | 1.0.0 | 1.0.7 | LangChain Vulnerable to Template Injection via Attribute Access in Prompt Templates ## Context
A template injection vulnerability exists in LangChain's prompt template system that allows attackers to access Python object internals through template syntax. This vulnerability affects applications that accept **untrusted template strings** (not just template variables) in `ChatPromptTemplate` and related prompt template classes.
Templates allow attribute access (`.`) and indexing (`[]`) but not method invocation (`()`).
The combination of attribute access and indexing may enable exploitation depending on which objects are passed to templates. When template variables are simple strings (the common case), the impact is limited. However, when using `MessagesPlaceholder` with chat message objects, attackers can traverse through object attributes and dictionary lookups (e.g., `__globals__`) to reach sensitive data such as environment variables.
The vulnerability specifically requires that applications accept **template strings** (the structure) from untrusted sources, not just **template variables** (the data). Most applications either do not use templates or else use hardcoded templates and are not vulnerable.
## Affected Components
- `langchain-core` package
- Template formats:
- F-string templates (`template_format="f-string"`) - **Vulnerability fixed**
- Mustache templates (`template_format="mustache"`) - **Defensive hardening**
- Jinja2 templates (`template_format="jinja2"`) - **Defensive hardening**
### Impact
Attackers who can control template strings (not just template variables) can:
- Access Python object attributes and internal properties via attribute traversal
- Extract sensitive information from object internals (e.g., `__class__`, `__globals__`)
- Potentially escalate to more severe attacks depending on the objects passed to templates
### Attack Vectors
#### 1. F-string Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{msg.__class__.__name__}")],
template_format="f-string"
)
# Note that this requires passing a placeholder variable for "msg.__class__.__name__".
result = malicious_template.invoke({"msg": "foo", "msg.__class__.__name__": "safe_placeholder"})
# Previously returned
# >>> result.messages[0].content
# >>> 'str'
```
#### 2. Mustache Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage
msg = HumanMessage("Hello")
# Attacker controls the template string
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{{question.__class__.__name__}}")],
template_format="mustache"
)
result = malicious_template.invoke({"question": msg})
# Previously returned: "HumanMessage" (getattr() exposed internals)
```
#### 3. Jinja2 Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage
msg = HumanMessage("Hello")
# Attacker controls the template string
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{{question.parse_raw}}")],
template_format="jinja2"
)
result = malicious_template.invoke({"question": msg})
# Could access non-dunder attributes/methods on objects
```
### Root Cause
1. **F-string templates**: The implementation used Python's `string.Formatter().parse()` to extract variable names from template strings. This method returns the complete field expression, including attribute access syntax:
```python
from string import Formatter
template = "{msg.__class__} and {x}"
print([var_name for (_, var_name, _, _) in Formatter().parse(template)])
# Returns: ['msg.__class__', 'x']
```
The extracted names were not validated to ensure they were simple identifiers. As a result, template strings containing attribute traversal and indexing expressions (e.g., `{obj.__class__.__name__}` or `{obj.method.__globals__[os]}`) were accepted and subsequently evaluated during formatting. While f-string templates do not support method calls with `()`, they do support `[]` indexing, which could allow traversal through dictionaries like `__globals__` to reach sensitive objects.
2. **Mustache templates**: By design, used `getattr()` as a fallback to support accessing attributes on objects (e.g., `{{user.name}}` on a User object). However, we decided to restrict this to simpler primitives that subclass dict, list, and tuple types as defensive hardening, since untrusted templates could exploit attribute access to reach internal properties like class on arbitrary objects
3. **Jinja2 templates**: Jinja2's default `SandboxedEnvironment` blocks dunder attributes (e.g., `__class__`) but permits access to other attributes and methods on objects. While Jinja2 templates in LangChain are typically used with trusted template strings, as a defense-in-depth measure, we've restricted the environment to block all attribute and method access on objects
passed to templates.
## Who Is Affected?
### High Risk Scenarios
You are affected if your application:
- Accepts template strings from untrusted sources (user input, external APIs, databases)
- Dynamically constructs prompt templates based on user-provided patterns
- Allows users to customize or create prompt templates
**Example vulnerable code:**
```python
# User controls the template string itself
user_template_string = request.json.get("template") # DANGEROUS
prompt = ChatPromptTemplate.from_messages(
[("human", user_template_string)],
template_format="mustache"
)
result = prompt.invoke({"data": sensitive_object})
```
### Low/No Risk Scenarios
You are **NOT** affected if:
- Template strings are hardcoded in your application code
- Template strings come only from trusted, controlled sources
- Users can only provide **values** for template variables, not the template structure itself
**Example safe code:**
```python
# Template is hardcoded - users only control variables
prompt = ChatPromptTemplate.from_messages(
[("human", "User question: {question}")], # SAFE
template_format="f-string"
)
# User input only fills the 'question' variable
result = prompt.invoke({"question": user_input})
```
## The Fix
### F-string Templates
F-string templates had a clear vulnerability where attribute access syntax was exploitable. We've added strict validation to prevent this:
- Added validation to enforce that variable names must be valid Python identifiers
- Rejects syntax like `{obj.attr}`, `{obj[0]}`, or `{obj.__class__}`
- Only allows simple variable names: `{variable_name}`
```python
# After fix - these are rejected at template creation time
ChatPromptTemplate.from_messages(
[("human", "{msg.__class__}")], # ValueError: Invalid variable name
template_format="f-string"
)
```
### Mustache Templates (Defensive Hardening)
As defensive hardening, we've restricted what Mustache templates support to reduce the attack surface:
- Replaced `getattr()` fallback with strict type checking
- Only allows traversal into `dict`, `list`, and `tuple` types
- Blocks attribute access on arbitrary Python objects
```python
# After hardening - attribute access returns empty string
prompt = ChatPromptTemplate.from_messages(
[("human", "{{msg.__class__}}")],
template_format="mustache"
)
result = prompt.invoke({"msg": HumanMessage("test")})
# Returns: "" (access blocked)
```
### Jinja2 Templates (Defensive Hardening)
As defensive hardening, we've significantly restricted Jinja2 template capabilities:
- Introduced `_RestrictedSandboxedEnvironment` that blocks **ALL** attribute/method access
- Only allows simple variable lookups from the context dictionary
- Raises `SecurityError` on any attribute access attempt
```python
# After hardening - all attribute access is blocked
prompt = ChatPromptTemplate.from_messages(
[("human", "{{msg.content}}")],
template_format="jinja2"
)
# Raises SecurityErr | ||
| medium | 1.0.0 | 1.2.5 | LangChain serialization injection vulnerability enables secret extraction in dumps/loads APIs ## Summary
A serialization injection vulnerability exists in LangChain's `dumps()` and `dumpd()` functions. The functions do not escape dictionaries with `'lc'` keys when serializing free-form dictionaries. The `'lc'` key is used internally by LangChain to mark serialized objects. When user-controlled data contains this key structure, it is treated as a legitimate LangChain object during deserialization rather than plain user data.
### Attack surface
The core vulnerability was in `dumps()` and `dumpd()`: these functions failed to escape user-controlled dictionaries containing `'lc'` keys. When this unescaped data was later deserialized via `load()` or `loads()`, the injected structures were treated as legitimate LangChain objects rather than plain user data.
This escaping bug enabled several attack vectors:
1. **Injection via user data**: Malicious LangChain object structures could be injected through user-controlled fields like `metadata`, `additional_kwargs`, or `response_metadata`
2. **Class instantiation within trusted namespaces**: Injected manifests could instantiate any `Serializable` subclass, but only within the pre-approved trusted namespaces (`langchain_core`, `langchain`, `langchain_community`). This includes classes with side effects in `__init__` (network calls, file operations, etc.). Note that namespace validation was already enforced before this patch, so arbitrary classes outside these trusted namespaces could not be instantiated.
### Security hardening
This patch fixes the escaping bug in `dumps()` and `dumpd()` and introduces new restrictive defaults in `load()` and `loads()`: allowlist enforcement via `allowed_objects="core"` (restricted to [serialization mappings](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/load/mapping.py)), `secrets_from_env` changed from `True` to `False`, and default Jinja2 template blocking via `init_validator`. These are breaking changes for some use cases.
## Who is affected?
Applications are vulnerable if they:
1. **Use `astream_events(version="v1")`** — The v1 implementation internally uses vulnerable serialization. Note: `astream_events(version="v2")` is not vulnerable.
2. **Use `Runnable.astream_log()`** — This method internally uses vulnerable serialization for streaming outputs.
3. **Call `dumps()` or `dumpd()` on untrusted data, then deserialize with `load()` or `loads()`** — Trusting your own serialization output makes you vulnerable if user-controlled data (e.g., from LLM responses, metadata fields, or user inputs) contains `'lc'` key structures.
4. **Deserialize untrusted data with `load()` or `loads()`** — Directly deserializing untrusted data that may contain injected `'lc'` structures.
5. **Use `RunnableWithMessageHistory`** — Internal serialization in message history handling.
6. **Use `InMemoryVectorStore.load()`** to deserialize untrusted documents.
7. Load untrusted generations from cache using **`langchain-community` caches**.
8. Load untrusted manifests from the LangChain Hub via **`hub.pull`**.
9. Use **`StringRunEvaluatorChain`** on untrusted runs.
10. Use **`create_lc_store`** or **`create_kv_docstore`** with untrusted documents.
11. Use **`MultiVectorRetriever`** with byte stores containing untrusted documents.
12. Use **`LangSmithRunChatLoader`** with runs containing untrusted messages.
The most common attack vector is through **LLM response fields** like `additional_kwargs` or `response_metadata`, which can be controlled via prompt injection and then serialized/deserialized in streaming operations.
## Impact
Attackers who control serialized data can extract environment variable secrets by injecting `{"lc": 1, "type": "secret", "id": ["ENV_VAR"]}` to load environment variables during deserialization (when `secrets_from_env=True`, which was the old default). They can also instantiate classes with controlled parameters by injecting constructor structures to instantiate any class within trusted namespaces with attacker-controlled parameters, potentially triggering side effects such as network calls or file operations.
Key severity factors:
- Affects the serialization path - applications trusting their own serialization output are vulnerable
- Enables secret extraction when combined with `secrets_from_env=True` (the old default)
- LLM responses in `additional_kwargs` can be controlled via prompt injection
## Exploit example
```python
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!
```
## Security hardening changes (breaking changes)
This patch introduces three breaking changes to `load()` and `loads()`:
1. **New `allowed_objects` parameter** (defaults to `'core'`): Enforces allowlist of classes that can be deserialized. The `'all'` option corresponds to the list of objects [specified in `mappings.py`](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/load/mapping.py) while the `'core'` option limits to objects within `langchain_core`. We recommend that users explicitly specify which objects they want to allow for serialization/deserialization.
2. **`secrets_from_env` default changed from `True` to `False`**: Disables automatic secret loading from environment
3. **New `init_validator` parameter** (defaults to `default_init_validator`): Blocks Jinja2 templates by default
## Migration guide
### No changes needed for most users
If you're deserializing standard LangChain types (messages, documents, prompts, trusted partner integrations like `ChatOpenAI`, `ChatAnthropic`, etc.), your code will work without changes:
```python
from langchain_core.load import load
# Uses default allowlist from serialization mappings
obj = load(serialized_data)
```
### For custom classes
If you're deserializing custom classes not in the serialization mappings, add them to the allowlist:
```python
from langchain_core.load import load
from my_package import MyCustomClass
# Specify the classes you need
obj = load(serialized_data, allowed_objects=[MyCustomClass])
```
### For Jinja2 templates
Jinja2 templates are now blocked by default because they can execute arbitrary code. If you need Jinja2 templates, pass `init_validator=None`:
```python
from langchain_core.load import load
from langchain_core.prompts import PromptTemplate
obj = load(
serialized_data,
allowed_objects=[PromptTemplate],
init_validator=None
)
```
> [!WARNING]
> Only disable `init_validator` if you trust the serialized data. Jinja2 templates can execute arbitrary Python code.
### For secrets from environment
`secrets_from_env` now defaults to `False`. If you need to load secrets from environment variables:
```python
from langchain_core.load import load
obj = load(serialized_data, secrets_from_env=True)
```
## Credits
* Dumps bug was reported by @yardenporat
* Changes for security hardening due to findings from @0xn3va and @VladimirEliTokarev | ||
| medium | 1.0.0 | 1.3.3 | LangChain vulnerable to unsafe deserialization of attacker-controlled objects through overly broad `load()` allowlists LangChain contains older runtime code paths that deserialize run inputs, run outputs, or other application-controlled payloads using overly broad object allowlists. These paths may call `load()` with `allowed_objects="all"`. This does not enable arbitrary Python object deserialization, but it does allow any trusted LangChain-serializable object to be revived, which is broader than these runtime paths require. As a result, attacker-supplied LangChain serialized constructor dictionaries may cause trusted runtime paths to instantiate classes with untrusted constructor arguments.
Applications are exposed only when all of the following are true:
1. The application accepts untrusted structured input, such as JSON, from a user or network request.
2. The application does not validate or canonicalize that input into an inert schema before invoking LangChain.
3. Attacker-controlled nested dictionaries or lists are preserved in LangChain run inputs or outputs.
4. The application uses an affected API path that later deserializes that run data.
Known affected runtime surfaces include:
- `RunnableWithMessageHistory`
- `astream_log()`
- `astream_events(version="v1")`
Related unsafe deserialization patterns may also affect applications that explicitly load serialized LangChain prompt or runnable objects from untrusted sources, including shared prompt stores, Hub artifacts with model configuration, or other application-controlled serialization stores.
Applications that validate incoming requests against a fixed schema, such as coercing user input to a plain string or message-content field before invoking LangChain, are unlikely to expose this deserialization primitive.
This release also fixes a related secret-marker validation bypass in the serialization and deserialization layer (`_is_lc_secret`). That issue creates an additional path by which attacker-controlled constructor dictionaries can avoid escaping during `dumps()` -> `loads()` round-trips and reach LangChain object revival logic.
## Impact
An attacker who can submit untrusted structured input to an affected application, and have that structure preserved in LangChain run data, may be able to inject LangChain serialized constructor payloads such as:
```json
{
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "ai", "AIMessage"],
"kwargs": {"content": "attacker-controlled content"}
}
```
If this payload reaches a broad `load()` call, LangChain may instantiate the referenced class instead of treating the payload as inert user data.
Realistic impacts include:
- Persistent chat-history poisoning when revived `AIMessage`, `HumanMessage`, or `SystemMessage` objects are stored by `RunnableWithMessageHistory`.
- Prompt injection or behavior manipulation if attacker-controlled messages are later included in model context.
- Instantiation of unexpected trusted LangChain objects with attacker-controlled constructor arguments.
- Possible credential disclosure or server-side requests if a reachable object reads environment credentials, creates clients, or contacts attacker-controlled endpoints during initialization.
- Additional prompt-template or runnable-configuration impacts in applications that separately load and execute untrusted serialized LangChain objects.
## Remediation
LangChain will deprecate the affected APIs as part of this fix:
- `RunnableWithMessageHistory`
- `astream_log()`
- `astream_events(version="v1")`
These are older code paths that are no longer recommended for new applications. They were not previously marked as deprecated, but recent LangChain documentation has primarily directed users toward newer streaming and memory patterns, including the `stream` API. Applications should migrate to the currently recommended APIs rather than continue depending on these older surfaces.
Separately, LangChain will update `load()` and `loads()` to tighten deserialization behavior so broad object revival is not applied implicitly to untrusted or application-controlled payloads. The older runtime surfaces listed above are being deprecated rather than preserved as supported paths for broad runtime deserialization.
This release also fixes a related secret-marker validation bypass in the serialization and deserialization layer (`_is_lc_secret`). That issue creates an additional path by which attacker-controlled constructor dictionaries can avoid escaping during `dumps()` -> `loads()` round-trips and reach LangChain object revival logic.
## Guidance for `load()` and `loads()`
`load()` and `loads()` should be used only with trusted LangChain manifests or serialized objects from trusted storage. Do not pass user-controlled data to `load()` or `loads()`, and do not use them as general parsers for request bodies, tool inputs, chat messages, or other attacker-controlled data.
`load()` and `loads()` are beta APIs, and their behavior may change as LangChain narrows unsafe defaults. Future LangChain versions will require callers to be explicit about which objects may be revived. Users should pass a narrow `allowed_objects` value appropriate for the specific trusted manifest they are loading, rather than relying on broad defaults or `allowed_objects="all"`, which permits the full trusted LangChain serialization allowlist.
## Credits
The original issue was first reported by @u-ktdi.
Similar findings were reported by @dewankpant, @shrutilohani, @Moaaz-0x, @pucagit.
A related `_is_lc_secret` marker bypass affecting `dumps()` -> `loads()` round-trips was reported by @yardenporat353 (and a similar report by @localhost-detect) | ||
| medium | 1.0.0a1 | 1.2.28 | LangChain has incomplete f-string validation in prompt templates LangChain's f-string prompt-template validation was incomplete in two respects.
First, some prompt template classes accepted f-string templates and formatted them without enforcing the same attribute-access validation as `PromptTemplate`. In particular, `DictPromptTemplate` and `ImagePromptTemplate` could accept templates containing attribute access or indexing expressions and subsequently evaluate those expressions during formatting.
Examples of the affected shape include:
```python
"{message.additional_kwargs[secret]}"
"https://example.com/{image.__class__.__name__}.png"
```
Second, f-string validation based on parsed top-level field names did not reject nested replacement fields inside format specifiers. For example:
```python
"{name:{name.__class__.__name__}}"
```
In this pattern, the nested replacement field appears in the format specifier rather than in the top-level field name. As a result, earlier validation based on parsed field names did not reject the template even though Python formatting would still attempt to resolve the nested expression at runtime.
## Affected usage
This issue is only relevant for applications that accept untrusted template strings, rather than only untrusted template variable values.
In addition, practical impact depends on what objects are passed into template formatting:
- If applications only format simple values such as strings and numbers, impact is limited and may only result in formatting errors.
- If applications format richer Python objects, attribute access and indexing may interact with internal object state during formatting.
In many deployments, these conditions are not commonly present together. Applications that allow end users to author arbitrary templates often expose only a narrow set of simple template variables, while applications that work with richer internal Python objects often keep template structure under developer control. As a result, the highest-impact scenario is plausible but is not representative of all LangChain applications.
Applications that use hardcoded templates or that only allow users to provide variable values are not affected by this issue.
## Impact
The direct issue in `DictPromptTemplate` and `ImagePromptTemplate` allowed attribute access and indexing expressions to survive template construction and then be evaluated during formatting. When richer Python objects were passed into formatting, this could expose internal fields or nested data to prompt output, model context, or logs.
The nested format-spec issue is narrower in scope. It bypassed the intended validation rules for f-string templates, but in simple cases it results in an invalid format specifier error rather than direct disclosure. Accordingly, its practical impact is lower than that of direct top-level attribute traversal.
Overall, the practical severity depends on deployment. Meaningful confidentiality impact requires attacker control over the template structure itself, and higher impact further depends on the surrounding application passing richer internal Python objects into formatting.
## Fix
The fix consists of two changes.
First, LangChain now applies f-string safety validation consistently to `DictPromptTemplate` and `ImagePromptTemplate`, so templates containing attribute access or indexing expressions are rejected during construction and deserialization.
Second, LangChain now rejects nested replacement fields inside f-string format specifiers.
Concretely, LangChain validates parsed f-string fields and raises an error for:
- variable names containing attribute access or indexing syntax such as `.` or `[]`
- format specifiers containing `{` or `}`
This blocks templates such as:
```python
"{message.additional_kwargs[secret]}"
"https://example.com/{image.__class__.__name__}.png"
"{name:{name.__class__.__name__}}"
```
The fix preserves ordinary f-string formatting features such as standard format specifiers and conversions, including examples like:
```python
"{value:.2f}"
"{value:>10}"
"{value!r}"
```
In addition, the explicit template-validation path now applies the same structural f-string checks before performing placeholder validation, ensuring that the security checks and validation checks remain aligned. | ||
| medium | any | 1.2.11 | LangChain affected by SSRF via image_url token counting in ChatOpenAI.get_num_tokens_from_messages ## Server-Side Request Forgery (SSRF) in ChatOpenAI Image Token Counting
### Summary
The `ChatOpenAI.get_num_tokens_from_messages()` method fetches arbitrary `image_url` values without validation when computing token counts for vision-enabled models. This allows attackers to trigger Server-Side Request Forgery (SSRF) attacks by providing malicious image URLs in user input.
### Severity
**Low** - The vulnerability allows SSRF attacks but has limited impact due to:
- Responses are not returned to the attacker (blind SSRF)
- Default 5-second timeout limits resource exhaustion
- Non-image responses fail at PIL image parsing
### Impact
An attacker who can control image URLs passed to `get_num_tokens_from_messages()` can:
- Trigger HTTP requests from the application server to arbitrary internal or external URLs
- Cause the server to access internal network resources (private IPs, cloud metadata endpoints)
- Cause minor resource consumption through image downloads (bounded by timeout)
**Note:** This vulnerability occurs during token counting, which may happen outside of model invocation (e.g., in logging, metrics, or token budgeting flows).
### Details
The vulnerable code path:
1. `get_num_tokens_from_messages()` processes messages containing `image_url` content blocks
2. For images without `detail: "low"`, it calls `_url_to_size()` to fetch the image and compute token counts
3. `_url_to_size()` performs `httpx.get(image_source)` on any URL without validation
4. Prior to the patch, there was no SSRF protection, size limits, or explicit timeout
**File:** `libs/partners/openai/langchain_openai/chat_models/base.py`
### Patches
The vulnerability has been patched in `langchain-openai==1.1.9` (requires `langchain-core==1.2.11`).
The patch adds:
1. **SSRF validation** using `langchain_core._security._ssrf_protection.validate_safe_url()` to block:
- Private IP ranges (RFC 1918, loopback, link-local)
- Cloud metadata endpoints (169.254.169.254, etc.)
- Invalid URL schemes
2. **Explicit size limits** (50 MB maximum, matching OpenAI's payload limit)
3. **Explicit timeout** (5 seconds, same as `httpx.get` default)
4. **Allow disabling image fetching** via `allow_fetching_images=False` parameter
### Workarounds
If you cannot upgrade immediately:
1. **Sanitize input:** Validate and filter `image_url` values before passing messages to token counting or model invocation
2. **Use network controls:** Implement egress filtering to prevent outbound requests to private IPs | ||
| medium | any | 1.2.22 | PYSEC-2026-2193: advisory LangChain is a framework for building agents and LLM-powered applications. Prior to version 1.2.22, multiple functions in langchain_core.prompts.loading read files from paths embedded in deserialized config dicts without validating against directory traversal or absolute path injection. When an application passes user-influenced prompt configurations to load_prompt() or load_prompt_from_config(), an attacker can read arbitrary files on the host filesystem, constrained only by file-extension checks (.txt for templates, .json/.yaml for examples). This issue has been patched in version 1.2.22. | fixed | osv:PYSEC-2026-2193 |
| medium | any | 0.1.35 | LangChain's XMLOutputParser vulnerable to XML Entity Expansion The XMLOutputParser in LangChain uses the etree module from the XML parser in the standard python library which has some XML vulnerabilities; see: https://docs.python.org/3/library/xml.html
This primarily affects users that combine an LLM (or agent) with the `XMLOutputParser` and expose the component via an endpoint on a web-service.
This would allow a malicious party to attempt to manipulate the LLM to produce a malicious payload for the parser that would compromise the availability of the service.
A successful attack is predicated on:
1. Usage of XMLOutputParser
2. Passing of malicious input into the XMLOutputParser either directly or by trying to manipulate an LLM to do so on the users behalf
3. Exposing the component via a web-service | fixed | osv:PYSEC-2026-1519 |
| medium | 1.0.0 | 1.0.7 | LangChain Vulnerable to Template Injection via Attribute Access in Prompt Templates ## Context
A template injection vulnerability exists in LangChain's prompt template system that allows attackers to access Python object internals through template syntax. This vulnerability affects applications that accept **untrusted template strings** (not just template variables) in `ChatPromptTemplate` and related prompt template classes.
Templates allow attribute access (`.`) and indexing (`[]`) but not method invocation (`()`).
The combination of attribute access and indexing may enable exploitation depending on which objects are passed to templates. When template variables are simple strings (the common case), the impact is limited. However, when using `MessagesPlaceholder` with chat message objects, attackers can traverse through object attributes and dictionary lookups (e.g., `__globals__`) to reach sensitive data such as environment variables.
The vulnerability specifically requires that applications accept **template strings** (the structure) from untrusted sources, not just **template variables** (the data). Most applications either do not use templates or else use hardcoded templates and are not vulnerable.
## Affected Components
- `langchain-core` package
- Template formats:
- F-string templates (`template_format="f-string"`) - **Vulnerability fixed**
- Mustache templates (`template_format="mustache"`) - **Defensive hardening**
- Jinja2 templates (`template_format="jinja2"`) - **Defensive hardening**
### Impact
Attackers who can control template strings (not just template variables) can:
- Access Python object attributes and internal properties via attribute traversal
- Extract sensitive information from object internals (e.g., `__class__`, `__globals__`)
- Potentially escalate to more severe attacks depending on the objects passed to templates
### Attack Vectors
#### 1. F-string Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{msg.__class__.__name__}")],
template_format="f-string"
)
# Note that this requires passing a placeholder variable for "msg.__class__.__name__".
result = malicious_template.invoke({"msg": "foo", "msg.__class__.__name__": "safe_placeholder"})
# Previously returned
# >>> result.messages[0].content
# >>> 'str'
```
#### 2. Mustache Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage
msg = HumanMessage("Hello")
# Attacker controls the template string
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{{question.__class__.__name__}}")],
template_format="mustache"
)
result = malicious_template.invoke({"question": msg})
# Previously returned: "HumanMessage" (getattr() exposed internals)
```
#### 3. Jinja2 Template Injection
**Before Fix:**
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage
msg = HumanMessage("Hello")
# Attacker controls the template string
malicious_template = ChatPromptTemplate.from_messages(
[("human", "{{question.parse_raw}}")],
template_format="jinja2"
)
result = malicious_template.invoke({"question": msg})
# Could access non-dunder attributes/methods on objects
```
### Root Cause
1. **F-string templates**: The implementation used Python's `string.Formatter().parse()` to extract variable names from template strings. This method returns the complete field expression, including attribute access syntax:
```python
from string import Formatter
template = "{msg.__class__} and {x}"
print([var_name for (_, var_name, _, _) in Formatter().parse(template)])
# Returns: ['msg.__class__', 'x']
```
The extracted names were not validated to ensure they were simple identifiers. As a result, template strings containing attribute traversal and indexing expressions (e.g., `{obj.__class__.__name__}` or `{obj.method.__globals__[os]}`) were accepted and subsequently evaluated during formatting. While f-string templates do not support method calls with `()`, they do support `[]` indexing, which could allow traversal through dictionaries like `__globals__` to reach sensitive objects.
2. **Mustache templates**: By design, used `getattr()` as a fallback to support accessing attributes on objects (e.g., `{{user.name}}` on a User object). However, we decided to restrict this to simpler primitives that subclass dict, list, and tuple types as defensive hardening, since untrusted templates could exploit attribute access to reach internal properties like class on arbitrary objects
3. **Jinja2 templates**: Jinja2's default `SandboxedEnvironment` blocks dunder attributes (e.g., `__class__`) but permits access to other attributes and methods on objects. While Jinja2 templates in LangChain are typically used with trusted template strings, as a defense-in-depth measure, we've restricted the environment to block all attribute and method access on objects
passed to templates.
## Who Is Affected?
### High Risk Scenarios
You are affected if your application:
- Accepts template strings from untrusted sources (user input, external APIs, databases)
- Dynamically constructs prompt templates based on user-provided patterns
- Allows users to customize or create prompt templates
**Example vulnerable code:**
```python
# User controls the template string itself
user_template_string = request.json.get("template") # DANGEROUS
prompt = ChatPromptTemplate.from_messages(
[("human", user_template_string)],
template_format="mustache"
)
result = prompt.invoke({"data": sensitive_object})
```
### Low/No Risk Scenarios
You are **NOT** affected if:
- Template strings are hardcoded in your application code
- Template strings come only from trusted, controlled sources
- Users can only provide **values** for template variables, not the template structure itself
**Example safe code:**
```python
# Template is hardcoded - users only control variables
prompt = ChatPromptTemplate.from_messages(
[("human", "User question: {question}")], # SAFE
template_format="f-string"
)
# User input only fills the 'question' variable
result = prompt.invoke({"question": user_input})
```
## The Fix
### F-string Templates
F-string templates had a clear vulnerability where attribute access syntax was exploitable. We've added strict validation to prevent this:
- Added validation to enforce that variable names must be valid Python identifiers
- Rejects syntax like `{obj.attr}`, `{obj[0]}`, or `{obj.__class__}`
- Only allows simple variable names: `{variable_name}`
```python
# After fix - these are rejected at template creation time
ChatPromptTemplate.from_messages(
[("human", "{msg.__class__}")], # ValueError: Invalid variable name
template_format="f-string"
)
```
### Mustache Templates (Defensive Hardening)
As defensive hardening, we've restricted what Mustache templates support to reduce the attack surface:
- Replaced `getattr()` fallback with strict type checking
- Only allows traversal into `dict`, `list`, and `tuple` types
- Blocks attribute access on arbitrary Python objects
```python
# After hardening - attribute access returns empty string
prompt = ChatPromptTemplate.from_messages(
[("human", "{{msg.__class__}}")],
template_format="mustache"
)
result = prompt.invoke({"msg": HumanMessage("test")})
# Returns: "" (access blocked)
```
### Jinja2 Templates (Defensive Hardening)
As defensive hardening, we've significantly restricted Jinja2 template capabilities:
- Introduced `_RestrictedSandboxedEnvironment` that blocks **ALL** attribute/method access
- Only allows simple variable lookups from the context dictionary
- Raises `SecurityError` on any attribute access attempt
```python
# After hardening - all attribute access is blocked
prompt = ChatPromptTemplate.from_messages(
[("human", "{{msg.content}}")],
template_format="jinja2"
)
# Raises SecurityErr | ||
| medium | 0.3.0 | 0.3.15 | langchain-core allows unauthorized users to read arbitrary files from the host file system A vulnerability in langchain-core versions >=0.1.17,<0.1.53, >=0.2.0,<0.2.43, and >=0.3.0,<0.3.15 allows unauthorized users to read arbitrary files from the host file system. The issue arises from the ability to create langchain_core.prompts.ImagePromptTemplate's (and by extension langchain_core.prompts.ChatPromptTemplate's) with input variables that can read any user-specified path from the server file system. If the outputs of these prompt templates are exposed to the user, either directly or through downstream model outputs, it can lead to the exposure of sensitive information. | fixed | osv:PYSEC-2026-1517 |
| medium | any | 0.1.11 | PYSEC-2024-45: advisory LangChain through 0.1.10 allows ../ directory traversal by an actor who is able to control the final part of the path parameter in a load_chain call. This bypasses the intended behavior of loading configurations only from the hwchase17/langchain-hub GitHub repository. The outcome can be disclosure of an API key for a large language model online service, or remote code execution. (A patch is available as of release 0.1.29 of langchain-core.) | fixed | osv:PYSEC-2024-45 |
| medium | any | 0.1.35 | LangChain's XMLOutputParser vulnerable to XML Entity Expansion The XMLOutputParser in LangChain uses the etree module from the XML parser in the standard python library which has some XML vulnerabilities; see: https://docs.python.org/3/library/xml.html
This primarily affects users that combine an LLM (or agent) with the `XMLOutputParser` and expose the component via an endpoint on a web-service.
This would allow a malicious party to attempt to manipulate the LLM to produce a malicious payload for the parser that would compromise the availability of the service.
A successful attack is predicated on:
1. Usage of XMLOutputParser
2. Passing of malicious input into the XMLOutputParser either directly or by trying to manipulate an LLM to do so on the users behalf
3. Exposing the component via a web-service | fixed | osv:GHSA-q84m-rmw3-4382 |
| medium | any | 0.3.84 | LangChain has incomplete f-string validation in prompt templates LangChain's f-string prompt-template validation was incomplete in two respects.
First, some prompt template classes accepted f-string templates and formatted them without enforcing the same attribute-access validation as `PromptTemplate`. In particular, `DictPromptTemplate` and `ImagePromptTemplate` could accept templates containing attribute access or indexing expressions and subsequently evaluate those expressions during formatting.
Examples of the affected shape include:
```python
"{message.additional_kwargs[secret]}"
"https://example.com/{image.__class__.__name__}.png"
```
Second, f-string validation based on parsed top-level field names did not reject nested replacement fields inside format specifiers. For example:
```python
"{name:{name.__class__.__name__}}"
```
In this pattern, the nested replacement field appears in the format specifier rather than in the top-level field name. As a result, earlier validation based on parsed field names did not reject the template even though Python formatting would still attempt to resolve the nested expression at runtime.
## Affected usage
This issue is only relevant for applications that accept untrusted template strings, rather than only untrusted template variable values.
In addition, practical impact depends on what objects are passed into template formatting:
- If applications only format simple values such as strings and numbers, impact is limited and may only result in formatting errors.
- If applications format richer Python objects, attribute access and indexing may interact with internal object state during formatting.
In many deployments, these conditions are not commonly present together. Applications that allow end users to author arbitrary templates often expose only a narrow set of simple template variables, while applications that work with richer internal Python objects often keep template structure under developer control. As a result, the highest-impact scenario is plausible but is not representative of all LangChain applications.
Applications that use hardcoded templates or that only allow users to provide variable values are not affected by this issue.
## Impact
The direct issue in `DictPromptTemplate` and `ImagePromptTemplate` allowed attribute access and indexing expressions to survive template construction and then be evaluated during formatting. When richer Python objects were passed into formatting, this could expose internal fields or nested data to prompt output, model context, or logs.
The nested format-spec issue is narrower in scope. It bypassed the intended validation rules for f-string templates, but in simple cases it results in an invalid format specifier error rather than direct disclosure. Accordingly, its practical impact is lower than that of direct top-level attribute traversal.
Overall, the practical severity depends on deployment. Meaningful confidentiality impact requires attacker control over the template structure itself, and higher impact further depends on the surrounding application passing richer internal Python objects into formatting.
## Fix
The fix consists of two changes.
First, LangChain now applies f-string safety validation consistently to `DictPromptTemplate` and `ImagePromptTemplate`, so templates containing attribute access or indexing expressions are rejected during construction and deserialization.
Second, LangChain now rejects nested replacement fields inside f-string format specifiers.
Concretely, LangChain validates parsed f-string fields and raises an error for:
- variable names containing attribute access or indexing syntax such as `.` or `[]`
- format specifiers containing `{` or `}`
This blocks templates such as:
```python
"{message.additional_kwargs[secret]}"
"https://example.com/{image.__class__.__name__}.png"
"{name:{name.__class__.__name__}}"
```
The fix preserves ordinary f-string formatting features such as standard format specifiers and conversions, including examples like:
```python
"{value:.2f}"
"{value:>10}"
"{value!r}"
```
In addition, the explicit template-validation path now applies the same structural f-string checks before performing placeholder validation, ensuring that the security checks and validation checks remain aligned. | ||
| medium | 0.1.17 | 0.1.53 | langchain-core allows unauthorized users to read arbitrary files from the host file system A vulnerability in langchain-core versions >=0.1.17,<0.1.53, >=0.2.0,<0.2.43, and >=0.3.0,<0.3.15 allows unauthorized users to read arbitrary files from the host file system. The issue arises from the ability to create langchain_core.prompts.ImagePromptTemplate's (and by extension langchain_core.prompts.ChatPromptTemplate's) with input variables that can read any user-specified path from the server file system. If the outputs of these prompt templates are exposed to the user, either directly or through downstream model outputs, it can lead to the exposure of sensitive information. | fixed | osv:GHSA-5chr-fjjv-38qv |
| low | any | 0.0.339 | LangChain directory traversal vulnerability LangChain through 0.1.10 allows ../ directory traversal by an actor who is able to control the final part of the path parameter in a load_chain call. This bypasses the intended behavior of loading configurations only from the hwchase17/langchain-hub GitHub repository. The outcome can be disclosure of an API key for a large language model online service, or remote code execution. | fixed | osv:GHSA-h59x-p739-982c |
| low | any | 1.2.11 | LangChain affected by SSRF via image_url token counting in ChatOpenAI.get_num_tokens_from_messages ## Server-Side Request Forgery (SSRF) in ChatOpenAI Image Token Counting
### Summary
The `ChatOpenAI.get_num_tokens_from_messages()` method fetches arbitrary `image_url` values without validation when computing token counts for vision-enabled models. This allows attackers to trigger Server-Side Request Forgery (SSRF) attacks by providing malicious image URLs in user input.
### Severity
**Low** - The vulnerability allows SSRF attacks but has limited impact due to:
- Responses are not returned to the attacker (blind SSRF)
- Default 5-second timeout limits resource exhaustion
- Non-image responses fail at PIL image parsing
### Impact
An attacker who can control image URLs passed to `get_num_tokens_from_messages()` can:
- Trigger HTTP requests from the application server to arbitrary internal or external URLs
- Cause the server to access internal network resources (private IPs, cloud metadata endpoints)
- Cause minor resource consumption through image downloads (bounded by timeout)
**Note:** This vulnerability occurs during token counting, which may happen outside of model invocation (e.g., in logging, metrics, or token budgeting flows).
### Details
The vulnerable code path:
1. `get_num_tokens_from_messages()` processes messages containing `image_url` content blocks
2. For images without `detail: "low"`, it calls `_url_to_size()` to fetch the image and compute token counts
3. `_url_to_size()` performs `httpx.get(image_source)` on any URL without validation
4. Prior to the patch, there was no SSRF protection, size limits, or explicit timeout
**File:** `libs/partners/openai/langchain_openai/chat_models/base.py`
### Patches
The vulnerability has been patched in `langchain-openai==1.1.9` (requires `langchain-core==1.2.11`).
The patch adds:
1. **SSRF validation** using `langchain_core._security._ssrf_protection.validate_safe_url()` to block:
- Private IP ranges (RFC 1918, loopback, link-local)
- Cloud metadata endpoints (169.254.169.254, etc.)
- Invalid URL schemes
2. **Explicit size limits** (50 MB maximum, matching OpenAI's payload limit)
3. **Explicit timeout** (5 seconds, same as `httpx.get` default)
4. **Allow disabling image fetching** via `allow_fetching_images=False` parameter
### Workarounds
If you cannot upgrade immediately:
1. **Sanitize input:** Validate and filter `image_url` values before passing messages to token counting or model invocation
2. **Use network controls:** Implement egress filtering to prevent outbound requests to private IPs | ||
| critical | 1.0.0 | 1.2.5 | LangChain serialization injection vulnerability enables secret extraction in dumps/loads APIs ## Summary
A serialization injection vulnerability exists in LangChain's `dumps()` and `dumpd()` functions. The functions do not escape dictionaries with `'lc'` keys when serializing free-form dictionaries. The `'lc'` key is used internally by LangChain to mark serialized objects. When user-controlled data contains this key structure, it is treated as a legitimate LangChain object during deserialization rather than plain user data.
### Attack surface
The core vulnerability was in `dumps()` and `dumpd()`: these functions failed to escape user-controlled dictionaries containing `'lc'` keys. When this unescaped data was later deserialized via `load()` or `loads()`, the injected structures were treated as legitimate LangChain objects rather than plain user data.
This escaping bug enabled several attack vectors:
1. **Injection via user data**: Malicious LangChain object structures could be injected through user-controlled fields like `metadata`, `additional_kwargs`, or `response_metadata`
2. **Class instantiation within trusted namespaces**: Injected manifests could instantiate any `Serializable` subclass, but only within the pre-approved trusted namespaces (`langchain_core`, `langchain`, `langchain_community`). This includes classes with side effects in `__init__` (network calls, file operations, etc.). Note that namespace validation was already enforced before this patch, so arbitrary classes outside these trusted namespaces could not be instantiated.
### Security hardening
This patch fixes the escaping bug in `dumps()` and `dumpd()` and introduces new restrictive defaults in `load()` and `loads()`: allowlist enforcement via `allowed_objects="core"` (restricted to [serialization mappings](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/load/mapping.py)), `secrets_from_env` changed from `True` to `False`, and default Jinja2 template blocking via `init_validator`. These are breaking changes for some use cases.
## Who is affected?
Applications are vulnerable if they:
1. **Use `astream_events(version="v1")`** — The v1 implementation internally uses vulnerable serialization. Note: `astream_events(version="v2")` is not vulnerable.
2. **Use `Runnable.astream_log()`** — This method internally uses vulnerable serialization for streaming outputs.
3. **Call `dumps()` or `dumpd()` on untrusted data, then deserialize with `load()` or `loads()`** — Trusting your own serialization output makes you vulnerable if user-controlled data (e.g., from LLM responses, metadata fields, or user inputs) contains `'lc'` key structures.
4. **Deserialize untrusted data with `load()` or `loads()`** — Directly deserializing untrusted data that may contain injected `'lc'` structures.
5. **Use `RunnableWithMessageHistory`** — Internal serialization in message history handling.
6. **Use `InMemoryVectorStore.load()`** to deserialize untrusted documents.
7. Load untrusted generations from cache using **`langchain-community` caches**.
8. Load untrusted manifests from the LangChain Hub via **`hub.pull`**.
9. Use **`StringRunEvaluatorChain`** on untrusted runs.
10. Use **`create_lc_store`** or **`create_kv_docstore`** with untrusted documents.
11. Use **`MultiVectorRetriever`** with byte stores containing untrusted documents.
12. Use **`LangSmithRunChatLoader`** with runs containing untrusted messages.
The most common attack vector is through **LLM response fields** like `additional_kwargs` or `response_metadata`, which can be controlled via prompt injection and then serialized/deserialized in streaming operations.
## Impact
Attackers who control serialized data can extract environment variable secrets by injecting `{"lc": 1, "type": "secret", "id": ["ENV_VAR"]}` to load environment variables during deserialization (when `secrets_from_env=True`, which was the old default). They can also instantiate classes with controlled parameters by injecting constructor structures to instantiate any class within trusted namespaces with attacker-controlled parameters, potentially triggering side effects such as network calls or file operations.
Key severity factors:
- Affects the serialization path - applications trusting their own serialization output are vulnerable
- Enables secret extraction when combined with `secrets_from_env=True` (the old default)
- LLM responses in `additional_kwargs` can be controlled via prompt injection
## Exploit example
```python
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!
```
## Security hardening changes (breaking changes)
This patch introduces three breaking changes to `load()` and `loads()`:
1. **New `allowed_objects` parameter** (defaults to `'core'`): Enforces allowlist of classes that can be deserialized. The `'all'` option corresponds to the list of objects [specified in `mappings.py`](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/load/mapping.py) while the `'core'` option limits to objects within `langchain_core`. We recommend that users explicitly specify which objects they want to allow for serialization/deserialization.
2. **`secrets_from_env` default changed from `True` to `False`**: Disables automatic secret loading from environment
3. **New `init_validator` parameter** (defaults to `default_init_validator`): Blocks Jinja2 templates by default
## Migration guide
### No changes needed for most users
If you're deserializing standard LangChain types (messages, documents, prompts, trusted partner integrations like `ChatOpenAI`, `ChatAnthropic`, etc.), your code will work without changes:
```python
from langchain_core.load import load
# Uses default allowlist from serialization mappings
obj = load(serialized_data)
```
### For custom classes
If you're deserializing custom classes not in the serialization mappings, add them to the allowlist:
```python
from langchain_core.load import load
from my_package import MyCustomClass
# Specify the classes you need
obj = load(serialized_data, allowed_objects=[MyCustomClass])
```
### For Jinja2 templates
Jinja2 templates are now blocked by default because they can execute arbitrary code. If you need Jinja2 templates, pass `init_validator=None`:
```python
from langchain_core.load import load
from langchain_core.prompts import PromptTemplate
obj = load(
serialized_data,
allowed_objects=[PromptTemplate],
init_validator=None
)
```
> [!WARNING]
> Only disable `init_validator` if you trust the serialized data. Jinja2 templates can execute arbitrary Python code.
### For secrets from environment
`secrets_from_env` now defaults to `False`. If you need to load secrets from environment variables:
```python
from langchain_core.load import load
obj = load(serialized_data, secrets_from_env=True)
```
## Credits
* Dumps bug was reported by @yardenporat
* Changes for security hardening due to findings from @0xn3va and @VladimirEliTokarev |
Get this data programmatically \u2014 free, no authentication.
curl https://depscope.dev/api/bugs/pypi/langchain_core| fixed |
| osv:GHSA-pjwx-r37v-7724 |
| fixed |
| osv:GHSA-6qv9-48xg-fc7f |
| fixed |
| osv:PYSEC-2026-373 |
| fixed |
| osv:PYSEC-2026-2564 |
| fixed |
| osv:PYSEC-2026-2563 |
| fixed |
| osv:PYSEC-2026-2562 |
| fixed |
| osv:PYSEC-2026-1518 |
| fixed |
| osv:GHSA-926x-3r5x-gfhw |
| fixed |
| osv:GHSA-2g6r-c272-w58r |
| fixed |
| osv:GHSA-c67j-w6g6-q2cm |