système : OPÉRATIONNEL
← retour à tous les hacks
AGENTS CRITICAL NEW

Un bac à sable eval() incomplet dans Langroid laisse un prompt exécuter du code

Le premier correctif de Langroid pour une faille d'injection de code dans TableChatAgent laissait un mode optionnel où le bac à sable eval() oublie de retirer les built-ins Python — rouvrant une exécution de code à distance non authentifiée.

2026-07-10 // 6 min affects: langroid, llm-agent-frameworks, pandas-eval

De quoi s’agit-il ?

Le 6 juillet 2026, un avis critique a été publié pour Langroid, un framework Python open source répandu pour construire des applications à base d’agents LLM. Son TableChatAgent permet à un modèle de répondre à des questions sur un jeu de données tabulaire en écrivant une expression pandas que l’agent évalue ensuite. La faille, référencée dans les bases d’avis GitLab et GitHub, permet à un attaquant capable d’influencer cette sortie du modèle d’obtenir une exécution de code à distance non authentifiée sur l’hôte. Elle est notée CVSS 10.0 — le maximum — car l’impact déborde du périmètre de sécurité du composant.

Ce cas est instructif par son historique. Le même agent avait déjà connu un problème d’injection de code corrigé en mai 2025, qui « assainit les entrées par défaut ». Le nouveau constat est un cas de mitigation incomplète : un mode d’évaluation optionnel a rouvert la brèche avec un bac à sable qui n’avait que l’apparence de la sûreté.

Comment ça marche

TableChatAgent évalue les expressions produites par le modèle avec le eval() de pandas. Lorsque le mode d’évaluation le plus permissif est activé, le code tente d’isoler l’exécution en fournissant au eval() intégré de Python un espace de noms local vide — l’intuition étant « pas de variables, pas de danger ». Le problème : vider les locals ne change rien aux globals. Si le dictionnaire des globals ne retire pas explicitement __builtins__, l’interpréteur Python réinjecte silencieusement l’intégralité de l’espace de noms des fonctions intégrées au moment de l’évaluation.

Cette seule omission rend à l’expression tout ce qui compte — y compris le mécanisme d’import — de sorte qu’un texte rédigé par le modèle peut atteindre os.system et lancer des commandes shell sous l’identité du processus. Puisque l’agent transmet la sortie du LLM directement à cet évaluateur, toute entrée qui oriente le modèle (un document empoisonné, une ligne hostile dans un jeu de données partagé, l’utilisateur d’un bot exposé au public) devient un chemin vers l’exécution de code.

# Anti-pattern CWE-94 : un bac à sable « vide » qui n'en est pas un
eval(expr, {}, {})          # globals={} -> Python réinjecte __builtins__
eval(expr, {"__builtins__": {}}, {})   # seul CECI les retire vraiment

Aucun payload opérationnel ici. La leçon est structurelle : vider les locals n’est pas un bac à sable. Retirer __builtins__ est le minimum, et même cela ne constitue pas une frontière robuste — Python offre des échappatoires bien documentées qui remontent vers __builtins__ via des chaînes d’attributs d’objets. Considérez « nous exécutons un eval() restreint » comme un signal d’alerte, pas comme un contrôle.

Pourquoi c’est important

C’est le mode de défaillance récurrent des frameworks d’agents : le modèle est un générateur de texte, mais le framework relie ce texte à une action — ici, du Python arbitraire. La frontière de confiance qui devrait séparer « le modèle a suggéré ceci » de « l’hôte a exécuté ceci » est absente ; influencer la sortie du modèle équivaut donc à influencer le shell.

L’angle du correctif incomplet compte aussi. Le premier patch faisait du comportement sûr la valeur par défaut, mais laissait un mode plus puissant accessible via un drapeau. Les équipes qui l’avaient activé pour gagner en capacité ont hérité d’une RCE de sévérité maximale, sans nouvel avertissement « unsafe » à l’appel. La puissance optionnelle est précisément là où se cachent les régressions, car ceux qui l’activent réauditent rarement la sûreté que le drapeau désactive.

Défenses

Mettez à jour. Le problème est corrigé dans Langroid 0.65.2 ; quittez toute version antérieure et figez-la dans votre lockfile.

N’utilisez jamais eval/exec sur du code généré par le modèle. Une sortie de modèle qui devient du code est, par définition, une entrée non fiable. Si vous pouvez éviter de l’exécuter, faites-le — préférez une interface contrainte et non-code (un DSL de requêtes structuré, un ensemble d’opérations paramétrées sur liste blanche) à « laissons le modèle écrire du Python que nous exécutons ».

Si vous devez évaluer des expressions, ne faites pas confiance à eval(). Utilisez un évaluateur restreint dédié, comme un interpréteur qui parcourt l’AST (par exemple asteval) ou des moteurs purement numériques (numexpr) qui n’exposent ni imports ni accès aux attributs. Passer {} en locals n’est pas une frontière ; même {"__builtins__": {}} n’est qu’un ralentisseur.

Isolez tout l’agent, pas seulement l’appel. Exécutez le processus dans un conteneur ou une VM jetable, sans identifiants, sans jetons cloud, avec un espace de travail en lecture seule et le réseau sortant refusé par défaut. Ainsi une évasion de l’évaluateur atterrit là où il n’y a rien à voler et personne à contacter.

Auditez vos drapeaux « unsafe ». Cherchez dans votre configuration les options d’évaluation, full_eval, trust_remote_code et autres opt-ins. Chacune doit avoir un responsable capable d’expliquer quelle protection elle désactive et pourquoi le risque est accepté.

Analysez vos dépendances. Des vérifications automatiques d’avis de sécurité en CI auraient signalé ceci le jour de sa publication. Faites de l’analyse d’avis une barrière, pas un tableau de bord.

Statut

ÉlémentDétail
ConcernéÉvaluation de code de TableChatAgent et VectorStore de Langroid (mode full_eval)
Cause racineeval() avec locals vides mais __builtins__ non retiré des globals (CWE-94)
ImpactExécution de code à distance non authentifiée sur l’hôte ; CVSS 10.0 (périmètre modifié)
Versions concernéesToutes les versions antérieures à 0.65.2
Corrigé dans0.65.2
RéférencesCVE-2026-54769 / GHSA-q9p7-wqxg-mrhc ; correctif incomplet précédent CVE-2025-46724 / GHSA-jqq5-wc57-f8hj (0.53.15, mai 2025)
ExploitationAucun PoC public ni exploitation connue au moment de la divulgation

Cet article résume une recherche publiquement divulguée et corrigée, et ne contient aucune instruction d’attaque opérationnelle.

Sources