The archive/CVE-2023-36258
langchain-ai/langchainDisclosed 2023

CVE-2023-36258:langchain: Arbitrary code execution / code injection

A reconstruction of the flaw, and the evidence path Lachesis follows to make it visible.

Historical recordv0.0.235 (affected: < 0.0.236 per NVD; further exec hardening landed through 0.0.247)
WeaknessCWE-94 · Arbitrary code execution / code injection
ImpactA arbitrary code execution / code injection in langchain, reachable from attacker-controlled input.
Read the reconstruction
01What happened

PALChain feeds LLM-generated Python straight into PythonREPL exec() with no validation -- host RCE.

That is the shape of the failure. The rest of this case file follows the evidence behind it.

Separate the record from the run

What was known.
What Lachesis established.

01Historical record

Known before Lachesis ran

The CVE and vulnerable release were selected from public history. We are not claiming novel discovery.

CVE
CVE-2023-36258
Vulnerable target
v0.0.235 (affected: < 0.0.236 per NVD; further exec hardening landed through 0.0.247) · vulnerable
Prior knowledge
CVE-2023-36258 · v0.0.235 (affected: < 0.0.236 per NVD; further exec hardening landed through 0.0.247) is vulnerable
then
02Lachesis run
Verified captured run

Independent re-detection

The experiment graph was built around the LangChain package tree at the vulnerable release (langchain 0.0.235, the last release before the 0.0.236 fix), rather than starting from arbitrary source. We did not point the tool at the CVE. Enumeration ran over the whole candidate registry (31 constructors, every family) with the pass-3 ranker ordering leads; 1234 candidates were enumerated and the injection.exec (code-injection) family surfaced on its own alongside the filesystem, lifecycle, navigation, object-integrity and query-injection families -- 35 injection.exec.escaping observations, the top-ranked of which is a Template(...) construction in the Nebula graph adapter. Every field under sast_output is the enumerator's own emission (candidate capsule plus sources_of value-flow cone); everything under adjudication is my reading of the source and the advisory and is labelled as such. Note the honesty caveat below: the anchored top sink is a hardcoded query-template constant, whereas the real CVE is PALChain feeding LLM-generated Python into PythonREPL exec() -- a sink the same family enumerated further down its own list (utilities/python.py:19).

Seeded inputs
None
Run timestamp
2026-09-02T14:17:24Z
Evidence artifact
langchain.kuzu

The commands and outputs below come from this recorded Lachesis run.

The reconstruction, in one frame

What Lachesis reconstructed.

One historical repository. Four captured queries. A complete source-to-sink argument.

Lachesis Casefiles · Unbound ComputeVerified runCVE-2023-36258
PALChain feeds LLM-generated Python straight into PythonREPL exec() with no validation -- host RCE.
01Sourcecode = self.llm_chain.predict(stop=[self.stop], callbacks=_run_manager.get_child(), **inputs)
02Sinkres = repl.run(code + f"\n{self.get_answer_expr}")
03Missing guardres = repl.run(code + f"\n{self.get_answer_expr}") # code from llm_chain.predict (line 63), executed via exec (utilities/python.py:19) with no validation
$ lachesis sources_of → reaches → guard_dominance
langchain-ai/langchain · v0.0.235 (affected: < 0.0.236 per NVD; further exec hardening landed through 0.0.247) · vulnerableunboundcompute.com
This was known history. Your code is not.

Lachesis follows the same evidence path through unfamiliar repositories.

The Lachesis reconstruction

Here is how the path becomes visible.

These are the recorded questions Lachesis asked of the historical vulnerable code, followed by the raw result and source location each query returned.

Repositorylangchain-ai/langchain
Revisionv0.0.235 (affected: < 0.0.236 per NVD; further exec hardening landed through 0.0.247) · vulnerable
EngineLachesis

Captured runThis is a real replay of Lachesis over the graph we built for this case (langchain 0.0.235). Every command below was run against that graph and every result is captured output (whole run elapsed 424.2s). We did not point the tool at a file or a family. The hunt enumerated the whole taxonomy (1234 candidates) and the code-injection family came out of that ordering. No payload was seeded; the finding was discovered from graph structure, so the mode is independent-redetection. The honest limitation -- that the top-ranked anchored sink is a hardcoded query-template constant rather than the PALChain exec path (which the same family enumerated at a lower rank) -- is recorded above and adjudicated below.

01 · load_graph01 / 05
Lachesis ran
load_graph --path langchain.kuzu --profile all && candidate_census

Load the graph and list every bug family

First we load the graph and ask the tool to list every bug family it knows, with nothing chosen ahead of time. It reports all 8 domains and 31 constructors and enumerates 1234 candidates. injection.exec (code-injection) is one family among many, not a target we selected.

Captured resultCaptured · load_graph
raw captured outputload_graph
[lachesis-mcp] loaded the graph (published 217203 nodes and 671286 edges, cpython-ast frontend)

CANDIDATE_CENSUS
taxonomy (8): lifecycle, memory, injection, navigation, object-integrity, filesystem, crypto-config, resource
constructors (31): complete_for_observable_graph=True
1234 candidates enumerated; families_that_fired includes injection.exec.escaping=35
langchain/graphs/nebula_graph.pyline 0
Code location returned for this evidence step.
The repair

A small line with a large consequence.

no AST allow/deny-list rejects system/exec/eval or imports before execution, and no timeout bounds the exec; any Python the LLM emits runs with full host privileges.

Before
res = repl.run(code + f"\n{self.get_answer_expr}")   # code from llm_chain.predict (line 63), executed via exec (utilities/python.py:19) with no validation
After the patch
// always validate generated code before execution -- PALChain.validate_code(code, self.code_validations) with a PALValidation allow/deny-list, plus a PythonREPL.run timeout running code in an isolated process (fixed in 0.0.236, commit e294ba4, PR #6003; hardened through 0.0.247).
Sources & further reading

The original record.

Read the historical advisory and vulnerability record behind this reconstruction.

Open the Lachesis quickstart