Three LangChain CVEs in 30 Days: SSRF, Path Traversal, and SQL Injection
March 2026 disclosed three independent ways to drain secrets from LangChain. RecursiveUrlLoader redirects past SSRF guards. load_prompt traverses paths. LangGraph SQLite checkpoints take SQL injection. Here's every CVE and patch.
March 2026 disclosed three independent ways to drain secrets from a LangChain deployment. Each one exposes a different class of enterprise data — filesystem files, environment secrets, conversation history. Each one was a CVE within a week of the others.
If you're running LangChain in production, you almost certainly use at least one of the affected components. Most teams use all three.
Here's what each CVE does, how to check if you're exposed, and what to do about it.
1. CVE-2026-27795 — RecursiveUrlLoader SSRF bypass
RecursiveUrlLoader is the LangChain helper that fetches a URL, parses links, and recursively crawls. It had SSRF protections — validate the input URL against an allowlist, refuse to fetch internal IPs. The patch was bypassed by HTTP redirects.
The attack:
# Attacker registers attacker-redirect.com # Configures it to respond with: # HTTP/1.1 302 Found # Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/ # Victim app: loader = RecursiveUrlLoader(url=user_supplied_url) docs = loader.load() # user_supplied_url = "https://attacker-redirect.com/innocent" # Initial URL passes the SSRF check (public domain, valid TLS). # Loader follows 302. # Loader fetches cloud metadata. # AWS credentials appear in docs.
The fix in the patched version: re-validate the final URL after every redirect. Until you upgrade, do the validation yourself with a custom HTTP client that wraps both requests and redirects.
ShipSafe's llm/langchain-recursive-url-loader-unsafe rule fires on any RecursiveUrlLoader invocation without an obvious allowlist or domain filter in context.
2. CVE-2026-34070 — load_prompt path traversal
load_prompt reads a prompt template from a file path. The vulnerable version accepts any path. Including ../../../etc/passwd,/proc/self/environ, or ~/.ssh/id_rsa.
If your code looks like this:
# Vulnerable
@app.post("/generate")
def generate(prompt_name: str):
prompt = load_prompt(f"prompts/{prompt_name}.yaml")
return run_chain(prompt)
# Attack: POST /generate { "prompt_name": "../../../etc/passwd#" }
# load_prompt reads the file and returns it as the prompt content.
# The LLM then includes the file contents in its response to the user.The fix is what it always is for path traversal: maintain an explicit allowlist of prompt names, look up the path server-side, never construct paths from request input.
# Patched pattern
PROMPTS = {
"summarize": "prompts/summarize.yaml",
"translate": "prompts/translate.yaml",
}
@app.post("/generate")
def generate(prompt_name: str):
if prompt_name not in PROMPTS:
raise HTTPException(400, "Unknown prompt")
prompt = load_prompt(PROMPTS[prompt_name])
return run_chain(prompt)3. CVE-2025-67644 — LangGraph SQLite checkpoint SQL injection
LangGraph's SQLite checkpoint implementation lets you persist agent state. The vulnerable version allows SQL injection through metadata filter keys — the dictionary keys you pass when filtering checkpoints get interpolated into SQL queries without parameterization.
# Vulnerable
checkpoints = saver.list(
config={"configurable": {"thread_id": "thread-1"}},
filter={user_supplied_key: "value"} # KEY is the injection point
)
# Attacker controls user_supplied_key:
# "x'; DROP TABLE checkpoints; --"
# The key gets concatenated into the WHERE clause.Mitigations: upgrade. If you can't immediately, validate every filter key against a known-good list of column names before passing it to the checkpoint saver.
This bug is a reminder that LLM frameworks aren't immune to the same kinds of bugs every web framework has been fighting for 25 years. SQL injection didn't go away because you put an agent in front of the database.
4. Audit your LangChain code in five commands
# 1. Current versions pip show langchain langchain-community langgraph 2>/dev/null npm ls @langchain/core @langchain/community 2>/dev/null # 2. RecursiveUrlLoader usage rg -n "RecursiveUrlLoader|recursive_url_loader" --type py --type ts # 3. load_prompt with dynamic paths rg -n "load_prompt|loadPrompt" --type py --type ts -A 1 | rg "req\.|request\.|body\.|user|input" # 4. LangGraph SQLite checkpoint rg -n "SqliteSaver|sqlite_saver" --type py # 5. Any pinned versions that are old? rg -n "langchain.*<\s*0\.3|langchain.*<\s*1" --type json --type txt
Each hit is a place to check the version, upgrade, or add wrapper safety. The RecursiveUrlLoader and load_prompt hits are the most urgent because the patches are backwards-compatible drop-in upgrades.
5. Defense beyond the patches
- Use an SSRF-safe HTTP client for everything that fetches URLs. Not just LangChain — any HTTP call that takes user-influenced input. Block RFC 1918, link-local, and cloud metadata ranges on the resolved IP, not just the hostname. Re-check after every redirect.
- Treat file paths as never user-supplied. Allowlist whatever you genuinely need, look up paths server-side. This applies to
load_prompt,open(),fs.readFile(), and every other file-reading call. - Use parameterized queries for everything. Even for "internal" dicts you control today. Internal becomes external faster than you expect.
- Add Dependabot or Renovate for LangChain/LangGraph specifically. These libraries ship CVE-grade patches several times a year. Don't manually track them.
The bottom line
LangChain is a useful library and a security target. Three CVEs in a month is a signal that the project is doing the right thing (responsible disclosure, fast patches) — not that you can skip upgrades. The CVEs are evidence the surface is well-attended; the patches need to land in your deployment for that to help you.
Upgrade. Wrap your HTTP and file calls. Add automated dependency monitoring.
Is your app cooked?
Paste your GitHub URL. 2 minutes. We'll tell you exactly what AI missed — free, no card.
Scan My App Free