LangChain-Core Orchestration Poisoning Lab (CVE-2023-36258 + CVE-2026-34070)
August 27, 2026 · View on GitHub
Overview
This laboratory demonstrates a critical Insecure Orchestration Vulnerability (OWASP Top 10 for LLMs: LLM06) within LangChain-Core (v1.2.24–v1.6.0). Students will explore how CVE-2026-34070 (direct path traversal) was patched in v1.2.25, while CVE-2023-36258 (symlink suffix validation) remains bypassed in all versions, and how the vendor's incomplete patch left the write-side .save() primitive exposed, enabling persistent Remote Code Execution (RCE) via AI Orchestration Poisoning.
Vulnerability Class: CWE-59 (Improper Link Resolution) → CWE-22 (Path Traversal) → CWE-94 (Code Injection)
CVSS v3.1: 10.0 Critical – AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Written by: Jeff Ponte (JDP Security)
Research Paper: JDP-2026-004: Architectural Boundary Failures in LangChain-Core
Core Learning Objectives
By completing this lab, students will:
- Bypass CVE-2026-34070 (Direct path traversal) – Understand how unvalidated file paths allowed arbitrary read access in version 1.2.24. This is patched in v1.2.25+.
- Bypass CVE-2023-36258 (Symlink suffix validation) – See how a
.jsonsymlink pointing toconfig.txtpasses extension checks. This remains exploitable in ALL versions. - Demonstrate the Incomplete Patch Flaw – Prove that PR #36471 fixed only direct path traversal but left symlink reads exposed, and that write-side
.save()was never protected. - Achieve RCE via Orchestration Poisoning – Overwrite framework source code to achieve persistent compromise (Confused Deputy attack).
- Understand the Write Primitive as a Standalone Vulnerability – Learn why the write-side issue has no CVE assigned and remains unpatched in every PyPI release as of August 2026.
Lab Architecture
The lab uses Docker containers running deliberately vulnerable versions of LangChain-Core. The symlinks (/app/config.json → /app/config.txt) are pre-created in the container images to simplify the lab; in a real attack an adversary would first use the write primitive to create such symlinks.
| Stage | Version | Direct Read (CVE-2026-34070) | Symlink Read (CVE-2023-36258) | Write Primitive (.save) |
|---|---|---|---|---|
| 0 | 1.2.24 | ❌ VULNERABLE | ❌ VULNERABLE | ❌ VULNERABLE |
| 1 | 1.2.25 | ✅ PATCHED | ❌ VULNERABLE (bypasses load_prompt_from_config) | ❌ VULNERABLE |
| 2 | 1.2.26 | ✅ PATCHED | ❌ VULNERABLE (bypasses load_prompt_from_config) | ❌ VULNERABLE |
| 3 | 1.2.27 | ✅ PATCHED | ❌ VULNERABLE (bypasses load_prompt_from_config) | ❌ VULNERABLE (PR #36585 incomplete) |
| 4 | latest | ✅ PATCHED | ❌ VULNERABLE (bypasses load_prompt_from_config) | ❌ VULNERABLE (PR #36585 incomplete) |
Key insight: Direct path traversal (
../../../../etc/passwd) is patched in Stage 1+.
Symlink read (../../../../app/config.json→config.txt) is NEVER patched.
Write primitive is NEVER patched.
Quick Start: Interactive Training Wizard
The interactive_trainer-3.py provides a menu-driven CLI that walks through all exploitation scenarios with built-in container management.
cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/langchain
chmod +x interactive_trainer-3.py
./interactive_trainer-3.py
Menu Options
HHealth Check – Verify the target's real LangChain version and environment.1Lesson 1: Baseline Arbitrary File Read – Perform direct path traversal to read/etc/passwd.2Lesson 2: Arbitrary File Write – Write a file outside the sandbox via traversal.3Lesson 3: Incomplete Patch –.save()Primitive – Demonstrate the write-side vulnerability.4Lesson 4: Data Exfiltration – Read admin credentials from/app/config.txtvia direct traversal (Stage 0 only) or/app/config.jsonsymlink (ALL stages).5Lesson 5: Symlink Traversal – Bypass extension checks via a.jsonsymlink.6Lesson 6: Full RCE Chain – Overwrite the framework's source code.7Lesson 7: Replicate.castRecording – Reproduce the exact symlink write bypass.8Lesson 8: Vendor's Fix Test – Show what PR #36585 actually fixes.9Lesson 9: Write Primitive Deep Dive – Explain the critical unpatched write vulnerability.SSwitch Target Stage – Move between stages 0–4.CStart Container – Launch the current stage's Docker container.XStop Container – Stop the running container.LView Container Logs – Inspect container output.QQuit – Exit the trainer.
Interactive Features
- Auto‑fill hints – Press Enter to use the default payload for each lesson.
- Single‑line JSON support – Paste a full JSON action or type a simple path.
- Real‑time evidence – After each request, the trainer displays the raw HTTP response and explains why the exploit succeeded or was blocked.
- Stage switching – Easily compare vulnerability behavior across versions.
Exercise 1: Automated Verification Suite
For a fully automated evaluation across all five stages, use the shell script:
cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/langchain
chmod +x verify_all.sh
./verify_all.sh
The script will:
- Start each stage container (ports 8000–8004).
- Run the same set of tests used in the interactive trainer (direct read, symlink read, write, symlink write).
- Print a clear summary table showing which vulnerabilities are present in each version.
- Stop and remove all containers when finished.
Exercise 2: Write Primitive Deep Dive (The Critical Unpatched Vulnerability)
Unlike the read-side CVEs, the write primitive has never been assigned a CVE and remains exploitable in every LangChain-Core release tested as of August 2026.
What PR #36585 Did (and Did Not) Fix
PR #36585 attempts to fix the write-side by resolving symlinks before checking file extensions:
- ✅ Blocks
exploit.json → target.py(different extensions) - ❌ Does NOT block
exploit.json → target.json(same extension)
Why it fails: The patch validates the resolved target extension, but does not check whether the final write destination is still inside the sandbox. A symlink named exploit.json pointing to a legitimate target.json passes every time. This allows an attacker to overwrite any .json file on the system, including LangChain's own package files.
OOB Verification
The write primitive is verified out-of-band by writing to /tmp/pwned.json and then reading the file back through the document_reader tool. The returned JSON includes the attacker-controlled template string.
Exercise 3: Symlink Read Bypass (CVE-2023-36258) — NEVER PATCHED
The container includes a pre-created symlink:
/app/config.json → /app/config.txt
Note: This symlink is pre-created in the container image to simplify the lab and focus on the vulnerability mechanics. In a real-world attack scenario, an adversary would first use the write primitive (also unpatched) to create the symlink themselves.
The framework validates that the path ends in .json, but because the document_reader tool does not resolve symlinks, the actual .txt credentials file is read. This demonstrates CWE-59: Improper Link Resolution Before File Access.
Stage-by-Stage Behavior
- Stage 0: Symlink read works because no validation exists.
- Stage 1+: The read-side patch (PR #36471) blocks direct traversal, but symlink reads remain vulnerable through all stages because the
document_readertool bypasses the patchedload_prompt_from_config()function.
Code Review: Why the Read Patch Is Incomplete
The partial fix in Stage 1 only validates paths used by load_prompt_from_config(), but other tools such as document_reader and file_writer do not use the same protection.
Vulnerable code path (simplified)
elif "Action: document_reader" in query:
# Bypass: document_reader opens files directly without canonicalization
path = match.group(1)
result = handle_document_reader(path)
The Write Primitive (never patched)
elif "Action: file_writer" in query:
# Stage 0-2: No destination validation at all
# Stage 3+: Only checks resolved extension, not destination
path = match.group(1)
content = match.group(2)
result = handle_file_writer(path, content)
The Whack‑a‑Mole Problem
Even after read-side patches, the write-side vulnerability persists because the fix only addresses one specific tool. The fundamental architectural issue remains: user-controlled paths are passed directly to filesystem operations without a centralized boundary check.
| Round | Patch | Bypass |
|---|---|---|
| 1 | PR #36471 adds allow_dangerous_paths=False to read-side | Symlink read bypass via document_reader tool |
| 2 | PR #36585 resolves symlinks before checking extension | .json → .json symlink bypass (write primitive) |
| 3 | (Not shipped) | Write primitive still exposed in all PyPI releases |
Root cause: The framework lacks a unified path anchoring layer that applies to all file operations, regardless of tool.
References & Additional Reading
- CVE-2026-34070: Path traversal in LangChain's
load_prompt_from_config - CVE-2023-36258: Suffix validation bypass via symlink
- Write Primitive (no CVE): Arbitrary file write via
PromptTemplate.save() - OWASP Top 10 for LLM Applications: LLM06 – Insecure Orchestration
- Research Paper: JDP-2026-004
- PR #36471: Read-side patch (incomplete)
- PR #36585: Write-side patch (insufficient)