LiteLLM Vient d'Être Piraté. J'ai Laissé Mon Agent IA Installer N'importe Quoi Pendant 8 Mois. Voici Ce Que J'ai Découvert.

9 min read

Jeudi dernier, j'ai demandé à Claude Code de me créer un outil de transcription. En 47 minutes, il a installé 30 packages Python sur ma machine. torch, scipy, huggingface_hub, numba, tiktoken. Et des dizaines de dépendances transitives dont j'ignore même le nom. J'ai approuvé chaque installation sans regarder. Comme d'habitude.

Puis j'apprends que litellm s'est fait pirater. 97 millions de téléchargements par mois, deux versions compromises sur PyPI, un fichier .pth qui s'exécute à chaque démarrage Python sans import. Clés SSH volées, tokens AWS, identifiants GCP, variables d'environnement. Découvert uniquement parce que le malware avait un bug qui plantait la machine.

Je n'utilisais pas litellm. Mais 59 packages installés en global pip sans venv, sans lockfile, sans audit, après 8 mois de dev quotidien avec un agent IA ? Le prochain litellm allait me tomber dessus. J'ai ouvert mon terminal pour voir ce qui traînait réellement sur ma machine. Voici ce que j'ai trouvé, et ce que j'ai corrigé en 45 minutes.

TLDR : Les agents de code IA ont supprimé la dernière friction humaine entre vous et les attaques de supply chain. Ils installent, vous approuvez, personne n'audite. J'ai scanné ma machine, trouvé 59 packages globaux non audités, migré vers uv avec des dépendances verrouillées par hash, et ajouté quatre lignes à mon CLAUDE.md qui restaurent la friction. Le tout en 45 minutes. Ouvrez votre terminal et lancez pip freeze avant votre prochaine session de code.

Deux développeurs explorant les risques de dépendances logicielles avec une illustration comique
Quand votre agent IA devient le gestionnaire de packages rebelle ultime 🤖🏴‍☠️

Ce qui est arrivé à LiteLLM (et pourquoi ça devrait vous foutre la trouille)

Le 24 mars, un groupe de menace appelé TeamPCP a publié les versions 1.82.7 et 1.82.8 de litellm sur PyPI via un compte de mainteneur volé. Le même groupe avait déjà compromis Trivy, un scanner de sécurité (oui, les gens qui hackent les outils de sécurité pour hacker vos outils). Le payload était un fichier .pth. Si vous ne savez pas ce que c'est : Python charge les fichiers .pth automatiquement au démarrage. Pas besoin d'import. Aucune trace dans votre code. Avoir le package installé suffisait.

Le malware récoltait les clés SSH, identifiants AWS, tokens GCP, configs Kubernetes, variables d'environnement et portefeuilles crypto. Il envoyait tout à un serveur externe à chaque démarrage Python. 97 millions de téléchargements par mois sur ce package.

Il a été découvert parce que l'attaquant a fait une erreur. Le malware avait un bug qui causait une fork bomb, l'usage RAM explosait, les machines plantaient. Un dev utilisant Cursor a remarqué que sa machine mourait, a tracé le problème jusqu'à un plugin MCP qui tirait litellm comme dépendance transitive. Transitive. Il n'avait jamais installé litellm lui-même. Son outil IA l'avait fait, via un plugin, via une chaîne de dépendances qu'il n'avait jamais vérifiée.

Nuance importante : l'image Docker officielle de litellm n'était pas affectée (deps épinglées). Le repo GitHub est resté propre. Seule la distribution PyPI était compromise. Mais c'est de là que la plupart des gens récupèrent leurs packages.

Si l'attaquant n'avait pas planté la machine, personne n'aurait rien remarqué. Pendant des semaines. Peut-être des mois.

J'ai demandé à mon agent IA de scanner ma propre machine

Quand j'ai lu l'histoire de litellm, ma première réaction a été de vérifier si j'étais touché. Ma deuxième réaction a été de réaliser que je n'avais aucun outil pour le faire.

Alors j'ai fait ce que je fais toujours. J'ai demandé à Claude Code. "Scanne ma machine pour les packages Python compromis." Ce qui a suivi était un peu gênant à regarder. L'agent a lancé une recherche récursive, a immédiatement touché des timeouts parce qu'iCloud synchronisait la moitié de mon système de fichiers. Des symlinks ralentissant ripgrep partout. Il a dû naviguer autour du sandboxing macOS juste pour regarder mes propres site-packages. Comme regarder quelqu'un essayer d'inspecter sa propre maison mais les portes se verrouillent toutes seules.

Le scan a trouvé 59 packages installés en global pip. Cinquante-neuf. J'en reconnaissais peut-être la moitié. Le reste, c'étaient des dépendances transitives de ces 47 minutes de construction d'outil de transcription, plus des mois d'autres moments "ouais, installe ça" accumulés par-dessus.

Puis j'ai demandé à Claude Code de lancer pip-audit. Il s'avère que pip-audit n'était pas installé. Je n'en avais jamais eu besoin avant (je n'y avais jamais pensé avant, plus précisément). Et c'est là que ça devient vraiment ironique : pour installer pip-audit, Claude Code a dû créer un venv temporaire parce que PEP 668 bloque les installations globales sur Python moderne. Le même PEP 668 que les 30 packages whisper avaient contourné avec --break-system-packages trois semaines plus tôt.

L'outil d'audit était bloqué par le système. Les 30 packages non audités étaient passés comme une lettre à la poste.

pip-audit a trouvé une CVE. Pygments 2.19.2, sévérité faible, une bibliothèque de rendu non exposée au réseau. Pas de litellm sur ma machine. Clean.

Mais clean par chance, pas par design. Et c'est là que le vrai problème commence.

Votre agent IA a supprimé la dernière friction qui vous protégeait

Avant les agents de code IA, vous tapiez pip install vous-même. C'était un moment conscient. Vous voyiez le nom du package. Vous aviez au moins une seconde pour vous demander si c'était le bon, si vous en aviez vraiment besoin, si peut-être vous devriez d'abord vérifier le nombre de téléchargements. La plupart du temps vous ne vérifiez pas. Mais la friction était là. Un ralentisseur entre vous et un potentiel compromis de supply chain.

Les agents IA ont complètement supprimé ce ralentisseur.

Sur ma machine, je peux retracer les deux vagues d'installation du projet de transcription. Première vague à 14h42, Claude Code a essayé openai-whisper. Deuxième vague à 15h45, il est passé à mlx-whisper parce que le premier ne marchait pas bien sur Apple Silicon. Deux arbres de dépendances complets superposés. Je n'ai vérifié ni l'un ni l'autre. Je faisais probablement du café pendant le deuxième.

C'est de la fatigue du consentement appliquée aux dépendances. L'agent propose, vous tapez "y", les packages affluent. Même mécanisme qui vous fait accepter les bannières de cookies sans les lire, sauf que la bannière de cookies ne peut pas voler vos clés SSH.

Le take d'Andrej Karpathy après litellm était direct : "yoink avec les LLM, moins de dépendances." Et il a raison en principe. Moins de surface d'attaque signifie moins de risque. Mais yoink ne résout pas le problème des dépendances que vous ne pouvez pas éliminer. litellm n'est pas un utilitaire de 50 lignes que vous pouvez copier-coller. torch non plus. scipy non plus. Certaines dépendances existent parce que l'alternative c'est réécrire une décennie de code C optimisé, et personne ne fait ça entre deux réunions un jeudi.

J'ai exploré le modèle CLAUDE.md à 3 couches (review, enforcement, intent) il y a quelques mois. Il s'avère qu'il manquait une couche : politique de sécurité des dépendances. Le modèle dit à l'agent comment écrire du code, mais ne dit rien sur ce qu'il a le droit d'installer.

Les agents n'ont pas créé un nouveau vecteur d'attaque. Les attaques de supply chain existaient bien avant Claude Code. Ce que les agents ont fait, c'est supprimer la friction qui vous en protégeait. C'est un amplificateur, pas une cause. Mais la conséquence est identique.

L'agent n'a pas créé la vulnérabilité. Il a supprimé la friction qui vous en protégeait.

Diagramme de chaîne de confiance montrant : Dev approuve agent → agent appelle pip install → pip résout l'arbre de dépendances (invisible au dev) → chaque nœud dans l'arbre est un point d'attaque potentiel. Contraste visuel entre le flux "avant agents" (friction consciente, dev voit le nom du package) et le flux "avec agents" (approbation automatique, arbre de dépendances caché). Style géométrique plat.
Chaîne de confiance : Avant vs. Après les agents IA

Ce que j'ai changé en 45 minutes (et ce qui vous protège vraiment)

Première chose que j'ai faite :

pip freeze > requirements-snapshot-20250325.txt

C'est tout. Un snapshot de tout ce qui est actuellement installé globalement. Pas un fix. Une photo de la scène de crime avant de commencer à nettoyer. Parce que vous devez savoir à quoi ressemble le bordel avant d'y toucher.

Puis j'ai parcouru le snapshot et épinglé chaque version exactement. Plus de >=. Plus de ~=. Chaque version verrouillée sur son numéro exact. Si vous avez requests>=2.28 dans n'importe quel fichier sur votre machine en ce moment, vous dites à pip "installe n'importe quelle version tant qu'elle est au-dessus de 2.28." Ça inclut une hypothétique 2.32 compromise que TeamPCP upload mardi prochain.

Ensuite : uv. Construit par Astral (les gens derrière Ruff). J'ai initialisé mon projet de transcription proprement cette fois :

uv init transcription-tool
cd transcription-tool
uv add mlx-whisper typer

Deux dépendances directes. uv les a résolues en 63 packages. Installé 42 (le reste sont des alternatives spécifiques à la plateforme). Et la partie qui compte : le lockfile contient des hashs SHA256 pour chaque package.

Le hash est la vraie protection. Ce qui s'est passé avec litellm : TeamPCP a uploadé une nouvelle version avec le même pattern de numéro de version sur PyPI. Si vous utilisiez pip avec >=, vous l'auriez tirée automatiquement. Mais si vous utilisez un lockfile avec des hashs, le contenu du package ne correspond pas au hash enregistré. uv sync refuse d'installer. Terminé. Attaque bloquée à la porte.

pip n'a pas de mécanisme équivalent par défaut. Vous pouvez utiliser --require-hashes, mais vous devez les générer et les maintenir vous-même. uv le fait automatiquement à chaque uv lock. Transparence totale : uv est un outil jeune (Astral l'a sorti en 2024), et l'écosystème autour bouge encore vite. Pip avec --require-hashes vous donne une protection similaire si vous préférez quelque chose de plus établi. L'important c'est le lockfile avec des hashs, pas l'outil spécifique.

Dernière étape : isolation venv par projet. Chaque projet a son propre environnement. Plus de pip global sur une machine qui a des clés SSH, des tokens API et des identifiants cloud qui traînent dans les variables d'environnement. J'ai aussi ajouté .nosync pour iCloud parce qu'avoir votre service cloud qui sync votre venv pendant que vous déboguez des conflits de dépendances, c'est le genre d'expérience que vous ne voulez pas vivre deux fois.

Avant : 30 packages en global pip. Aucune trace de ce qui les a installés ou quand.
Après : 2 dépendances. 63 résolues. Chacune avec un hash SHA256. Dans un venv isolé qui ne peut pas toucher le reste de mon système.

Les quatre lignes que j'ai ajoutées à mon CLAUDE.md

Les outils ne suffisent pas si l'agent qui installe des packages pour vous n'a aucune règle. Alors j'ai ajouté quatre lignes en haut de mon CLAUDE.md :

1. Ne jamais lancer pip/npm install sans approbation explicite de l'utilisateur
2. Avant de suggérer une nouvelle dépendance, lister ses sous-dépendances majeures
3. Préférer générer du code utilitaire plutôt qu'ajouter une dépendance quand la fonctionnalité fait moins de 200 lignes
4. Toujours épingler les versions exactes, jamais utiliser >= ou ~=

J'avais déjà "ne jamais installer sans demander" dans mon framework de contrats de prompt. Cette règle était nécessaire mais pas suffisante. J'approuvais encore aveuglément parce que je ne pouvais pas voir l'arbre de dépendances.

La règle 2 est celle qui restaure la friction. Quand Claude Code me dit "mlx-whisper a besoin de torch, numpy, scipy, huggingface_hub, tokenizers, et 25 dépendances transitives," j'ai à nouveau un moment conscient. Je peux décider si un outil de transcription vaut la peine de tirer la moitié de PyTorch sur ma machine. Peut-être que la réponse est encore oui. Mais au moins je décide, je ne tamponne pas aveuglément.

La règle 3 est le principe Karpathy avec un garde-fou. Si la fonctionnalité est assez simple, générez le code au lieu d'installer un package. Mais "assez simple" a besoin d'une limite, sinon vous finissez par réécrire des bibliothèques de cryptographie from scratch (s'il vous plaît ne le faites pas). Deux cents lignes est mon seuil. Le vôtre peut être différent.

La règle 4 est la plus basique et la plus ignorée. Chaque >= dans votre projet est une porte que vous avez laissée ouverte pour que quelqu'un d'autre puisse passer.

Ces quatre lignes ne sont pas magiques. Un agent suffisamment capable pourrait les contourner. Mais pour les agents de code IA de production actuels, c'est la différence entre installer aveuglément et installer en connaissance de cause. Ces règles ne protègent pas contre les packages déjà compromis dans vos deps existantes. Elles empêchent les futures installations aveugles. Auditer ce qui est déjà là (pip-audit, uv lock) est une étape séparée que vous devez encore faire.

La friction que votre agent a supprimée, vous la remettez avec quatre lignes dans votre CLAUDE.md.


Le prochain litellm arrive. Pas une question de "si." Et cette fois l'attaquant ne fera pas de bug qui plante votre RAM. Le malware sera silencieux, l'exfiltration discrète, et vous ne saurez pas que vos clés SSH sont parties jusqu'à ce que vous voyiez des commits que vous n'avez jamais faits.

Avant de vous faire avoir par le prochain, ouvrez votre terminal. Lancez pip freeze. Comptez les packages que vous ne reconnaissez pas. Installez uv. Créez un venv. Ouvrez votre CLAUDE.md et ajoutez quatre lignes avant votre prochaine session. 45 minutes. Coût zéro.

Votre agent de code IA est exactement aussi sécurisé que les règles que vous lui donnez. Pas de règles, pas de sécurité. Juste de la chance. Et la chance n'est pas un plan.

Sources : Thread GitHub du compromis supply chain LiteLLM : github.com/BerriAI/litellm/issues/9484 | Documentation uv : docs.astral.sh/uv

(*) La couverture est générée par IA. Mon vrai output terminal pendant le scan était beaucoup moins photogénique.