
Ho trovato una vulnerabilità zero-day in langchain — Ecco come è andata
Come un'API legacy dimenticata in uno dei framework AI più popolari ha esposto silenziosamente milioni di applicazioni a letture arbitrarie di file.
C'è una certa sensazione che provi quando una proof-of-concept funziona al primo tentativo. Non esattamente eccitazione — più una lenta e scomoda realizzazione che qualcosa di reale è appena successo. È quello che ho provato quando ho eseguito la mia configurazione di test contro load_prompt_from_config() e ho visto il contenuto di un file che non avrebbe dovuto leggere stampato direttamente sul mio terminale.
Questa è la storia di CVE-2026-34070: una vulnerabilità di path traversal in langchain-core, ora corretta nella versione 1.2.22.
Se hai costruito qualsiasi cosa con l'AI negli ultimi due anni, hai quasi certamente toccato LangChain. È il tessuto connettivo dello stack AI moderno — il framework che collega modelli linguistici, vector store, strumenti e gestione dei prompt in applicazioni coerenti. Con oltre 130k stelle su GitHub e adozione in tutto, dai progetti weekend improvvisati alle implementazioni enterprise, una vulnerabilità qui non rimane contenuta.
Stavo facendo una revisione del codice del sottosistema dei prompt quando qualcosa ha attirato la mia attenzione: un modulo chiamato langchain_core/prompts/loading.py. Stava caricando file dal disco basandosi su valori estratti direttamente da dizionari di configurazione deserializzati. Nessuna validazione. Nessuna sanificazione dei percorsi. Solo .
open(path)Ho continuato a leggere.
Tre funzioni interne erano al centro di questo problema:
_load_template() — legge i file referenziati da template_path, suffix_path e prefix_path_load_examples() — legge i file referenziati dalla chiave examples quando è una stringa_load_few_shot_prompt() — legge i file referenziati da example_prompt_pathI controlli sull'estensione dei file c'erano. .txt per i template. .json, .yaml, .yml per gli esempi. Ma non c'era nulla che impedisse a quei percorsi di essere assoluti (/etc/passwd) o basati su traversal (../../../../home/user/.ssh/). Il filtro sull'estensione dava un falso senso di sicurezza — significava solo che un attaccante doveva scegliere l'estensione giusta, non che l'attacco fosse bloccato.
Queste funzioni sono tutte raggiungibili tramite due API pubbliche: load_prompt() e load_prompt_from_config().
La versione più semplice era questa:
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) # contenuto di /tmp/secret.txt, stampato in modo pulito
Tutto qui. Passa un percorso assoluto, ottieni il contenuto del file avvolto in un PromptTemplate. Nessuna autenticazione. Nessun privilegio speciale. Se puoi influenzare il dizionario di configurazione, puoi leggere file.
Il directory traversal funzionava altrettanto pulitamente:
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
La variante JSON/YAML era probabilmente più pericolosa per il tipo di file che poteva raggiungere:
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 contiene le tue credenziali Docker Hub. ~/.azure/accessTokens.json ha i tuoi token Azure. Manifest Kubernetes, configurazioni CI/CD, impostazioni interne delle applicazioni — qualsiasi cosa con l'estensione giusta situata ovunque nel filesystem era un bersaglio legittimo.
Il punteggio CVSS è arrivato a 7.5 Alto, con un vettore AV:N/AC:L/PR:N/UI:N — accessibile dalla rete, bassa complessità, nessun privilegio, nessuna interazione utente richiesta.
Il punteggio è limitato sotto 9+ perché il controllo sull'estensione dei file limita effettivamente quali file possono essere letti. Ma 7.5 sottovaluta comunque l'impatto nel mondo reale in determinati modelli di implementazione.
Pensa a dove questo atterra in produzione:
load_prompt_from_config(), ogni utente è un potenziale attaccante.In quegli ambienti, la limitazione "vincolato dall'estensione del file" conta molto meno. Un attaccante può semplicemente puntare a file che sa esistere con l'estensione giusta. Su un'istanza cloud tipica: requirements.txt, config.yaml, .env.yaml, file segreti montati con estensioni .json — la lista è lunga.
Le funzioni interessate sono descritte nell'advisory come "API legacy non documentate." Precedono l'attuale sistema di serializzazione langchain_core.load (dumpd/dumps/load/loads), che usa un modello basato su allowlist e non esegue letture dal filesystem.
Le nuove API esistono. Sono migliori. Ma il vecchio codice non è mai stato ripulito — è rimasto lì, raggiungibile, senza validazione, in attesa.
Questo è un pattern a cui vale la pena prestare attenzione. Nei progetti open-source in rapida evoluzione, specialmente quelli cresciuti velocemente come LangChain, il debito tecnico si accumula negli angoli. Il codice legacy che "non era mai stato realmente pensato per gli utenti" non riceve la stessa attenzione della superficie API primaria. Ma è comunque chiamabile. È comunque nel pacchetto. E se legge file dal disco, è una potenziale vulnerabilità.
La patch è arrivata in langchain-core 1.2.22. La correzione aggiunge una validazione dei percorsi che rifiuta sia i percorsi assoluti sia le sequenze di traversal .. prima che qualsiasi file venga aperto. Un'uscita di sicurezza — allow_dangerous_paths=True — è disponibile per le applicazioni che hanno genuinamente bisogno di leggere da percorsi fidati, con l'esplicito riconoscimento che il chiamante sta accettando il rischio.
Le API legacy sono state anche formalmente deprecate con questa release. Saranno rimosse completamente nella 2.0.0. Se stai usando load_prompt() o load_prompt_from_config() ovunque, migra agli equivalenti langchain_core.load ora piuttosto che aspettare la modifica di rottura.
Aggiorna immediatamente:
pip install --upgrade langchain-core
Verifica di essere sulla 1.2.22 o successiva:
python -c "import langchain_core; print(langchain_core.__version__)"
Se stai costruendo su LangChain, vale la pena eseguire un rapido audit:
load_prompt e load_prompt_from_config nel tuo codebase. Se compaiono, controlla cosa viene passato loro..txt, .json o .yaml esistono sulle tue istanze e cosa contengono.I bug di sicurezza nell'infrastruttura AI conteranno sempre di più, non di meno, man mano che questi sistemi gestiscono carichi di lavoro più sensibili. Il team di LangChain ha risposto bene — la correzione è pulita, il percorso di deprecazione è chiaro e la documentazione dell'advisory è approfondita.
Ma è un buon promemoria che la superficie di attacco di un'applicazione AI non è solo il modello. È ogni libreria nello stack, ogni funzione legacy che non è mai stata ripulita, ogni posto dove "l'utente probabilmente non passerà input non fidato qui" si è rivelato un'assunzione piuttosto che una garanzia.
Leggi il codice. Soprattutto le parti vecchie.
CVE-2026-34070 è stata assegnata a questa vulnerabilità. L'advisory completo è disponibile nella LangChain GitHub Security Advisory.