Retour à tous les articles
Publié le · par Renaud Deraison

Vous avez vérifié les mauvaises quarante minutes

Le 14 août 2026, SOCRadar a publié une réattribution de la compromission de la chaîne d'approvisionnement LiteLLM : 95 % des organisations touchées avaient déjà été moissonnées quelques jours plus tôt, par le scanner Trivy d'Aqua Security lui-même. Cinq mois de réponse à incident calés sur la mauvaise fenêtre, parce que chaque contrôle de la chaîne avait besoin que quelqu'un nomme d'abord le paquet malveillant. Les couches chaîne d'approvisionnement et identifiants de Bromure Agentic Coding n'ont pas besoin du nom.

On vous a dit de vérifier si vous aviez installé un paquet Python pendant une fenêtre de quarante minutes, le 24 mars. Cinq mois plus tard, SOCRadar a découvert que 95 % des victimes avaient déjà été vidées cinq jours plus tôt, par leur scanner de vulnérabilités.

Le 14 août, Ionut Arghire, de SecurityWeek, a publié la réattribution par SOCRadar de l'une des plus grandes compromissions d'infrastructure de développement de l'année. SOCRadar détenait le jeu de données au niveau des enregistrements, avec les relevés de collecte par organisation pour 2 188 entités, et a posé une question que personne n'avait posée : à quel moment les données de chaque organisation sont-elles parties ?

Pour 2 085 d'entre elles, la collecte s'était achevée avant la publication des paquets malveillants dont tout le monde a été averti. Cela fait 95 %. La conclusion de SOCRadar tient en une phrase :

« Cette chronologie correspond à la compromission en amont de Trivy, pas à la fenêtre d'installation de LiteLLM. Les 40 minutes que tout le monde a rapportées étaient le dernier acte, pas la pièce entière. »

Les chercheurs avaient démonté la charge utile dès le mois de mars. SOCRadar a changé la chronologie. Pendant cinq mois, l'industrie a mené sa réponse à incident contre la mauvaise fenêtre, avec des contrôles incapables d'agir tant que personne n'avait nommé la chose.

Le dernier acte

Les fameuses quarante minutes appartiennent à LiteLLM. Le 24 mars, les versions 1.82.7 et 1.82.8 sont montées sur PyPI à 10 h 39 UTC et ont tenu une quarantaine de minutes avant que PyPI ne les mette en quarantaine. Les consignes de l'époque vous disaient de considérer comme suspecte toute installation faite jusqu'à 16 h 00 UTC ce jour-là.

La charge utile mérite qu'on s'arrête sur ce qu'elle met en échec. C'est un fichier .pth, litellm_init.pth, déposé dans site-packages. Python exécute les fichiers .pth au démarrage de l'interpréteur, que quoi que ce soit importe ou non le paquet qui les a installés. Il n'y a pas de setup.py à inspecter ni de script d'installation à retirer, donc --ignore-scripts et tous les contrôles bâtis sur le modèle d'exécution des scripts d'installation passent à côté. The Hacker News a couvert le mécanisme le 12 août et a mentionné l'identifiant au niveau de l'écosystème, CVE-2026-33634. La moisson partait vers models.litellm[.]cloud.

Les attaquants ont poussé ces wheels avec un jeton de publication PyPI volé, en contournant la propre chaîne de publication de LiteLLM. D'où venait ce jeton ?

La pièce entière

Trivy. Le scanner de vulnérabilités open source d'Aqua Security, que des dizaines de milliers de pipelines exécutent pour trouver précisément ce genre de problème.

L'avis d'Aqua, GHSA-69fq-xp46-6x23, n'adoucit pas la cause racine. Un acteur suivi sous le nom de TeamPCP (UNC6780 pour Google) a extrait un jeton d'accès personnel de l'environnement GitHub Actions de Trivy via un workflow pull_request_target, fin février. Aqua a divulgué cela le 1er mars et a fait tourner ses identifiants. L'avis précise ensuite que la rotation n'était « pas atomique (tous les identifiants n'ont pas été révoqués simultanément) ». L'attaquant s'est installé à l'intérieur de la rotation et a récolté les nouveaux secrets au fur et à mesure qu'Aqua les émettait.

Dix-neuf jours plus tard, il les a dépensés. Le 19 mars, il a force-pushé 76 des 77 tags de version d'aquasecurity/trivy-action, et les sept tags de aquasecurity/setup-trivy, sur des commits malveillants : un workflow épinglé sur @v0.28.0 récupérait donc du nouveau code sans que rien ne semble bouger dans le dépôt. Avec le compte de service compromis aqua-bot, il a publié Trivy v0.69.4 sur GHCR, ECR Public, Docker Hub, deb et rpm. Les images Docker Hub v0.69.5 et v0.69.6 ont suivi les 22 et 23 mars, sur un identifiant compromis séparément.

Le décorticage de StepSecurity décrit ce que faisait le voleur injecté une fois qu'un pipeline l'exécutait :

  • Lire l'environnement de chaque processus du runner via /proc/*/environ.
  • Vider la mémoire du processus worker du runner GitHub Actions via /proc/<pid>/mem, à l'aide de Python encodé en base64. Cela récupère les secrets qu'un runner masque dans ses logs, puisque le masquage est un filtre de journalisation et que la mémoire du processus contient les valeurs en clair.
  • Ratisser le système de fichiers à la recherche de clés privées SSH, d'identifiants git, de jetons AWS, GCP et Azure, de secrets Kubernetes, de configurations Docker, d'identifiants de bases de données, d'états Terraform et de portefeuilles de cryptomonnaie.
  • Sceller le tout sous une clé publique RSA-4096 codée en dur, en chiffrement hybride, et l'expédier vers scan.aquasecurtiy.org.

Relisez ce nom d'hôte. C'est le domaine du fournisseur lui-même, avec deux lettres interverties. Pour un flux de réputation de domaines, ou pour un analyste qui parcourt les sorties réseau à 2 h du matin, un scanner qui parle à quelque chose ressemblant à aquasecurity.org est la ligne la moins remarquable de la page.

Si l'envoi échouait, le voleur créait un dépôt public nommé tpcp-docs sur le compte GitHub de la victime et y attachait le butin en pièces jointes de release.

SOCRadar a daté la première collecte dix-huit minutes après la mise en ligne du build malveillant.

Mars 2026 — fenêtres d'exposition, à l'échelle28 févr.19 mars22–23 mars24 mars14 aoûtPAT volé · rotation non atomiquetrivy-action — 76 tags sur 77 réécrits~12 hsetup-trivy~4 hv0.69.4~3 hDocker Hub .5 / .6~10 hLiteLLM 1.82.7 / 1.82.840 min — la fenêtre vérifiée partout2 085 des 2 188 organisations — 95 % — collectées entièrement dans cet intervalleréattribuéFenêtres et versions issues de l'avis Aqua Security GHSA-69fq-xp46-6x23. Décomptes détaillés de SOCRadar,rapportés par SecurityWeek, 2026-08-14. L'axe horizontal est schématique à l'intérieur de chaque journée.
Chaque fenêtre d'artefact de la campagne de mars se comptait en heures, et chacune s'est refermée avant l'avis qui la nommait. La fenêtre sur laquelle le monde a agi, quarante minutes sur PyPI cinq jours plus tard, fut la dernière à s'ouvrir.

Ce qui est sorti des pipelines

Hudson Rock a obtenu l'archive et Help Net Security en a rapporté la taille le 13 août : 153 Go, 433 909 fichiers, 118 829 dumps de runners CI rattachés à 2 488 domaines d'entreprise. Le décompte indépendant de CloudSEK, environ 434 000 fichiers, situait l'exposition près de 2 500 organisations et plus de 430 000 pipelines. Alon Gal, CTO de Hudson Rock, a parlé d'un « effort mondial de divulgation éthique » et a dit que l'ampleur « nous pousse dans un monde entièrement nouveau quant au type de réponse requis ». La lecture de Kevin Beaumont : « C'est une brèche massive de la chaîne d'approvisionnement due à une mauvaise sécurité de l'IA. »

La ventilation par organisation de SOCRadar va plus loin. Plus d'un millier d'organisations ont perdu des JWT et des jetons d'authentification. Des centaines ont perdu des clés privées, des clés d'accès AWS, des jetons GitLab, des clés d'API OpenAI, des webhooks Slack, des jetons GitHub Actions et des clés d'API Google. Une organisation a perdu environ 3 477 secrets individuels. Onze cents ont exposé les adresses e-mail de leurs committers. Les runners étaient GitHub Actions, GitLab CI, Jenkins, Bitbucket, CircleCI et Buildkite. L'Allemagne, le Brésil et la France ont été les plus touchés, et les collectes se négocient désormais sur Telegram.

Chaque contrôle de cette chaîne avait besoin d'un nom

Alignez les défenses qui existaient en mars, et notez ce dont chacune avait besoin avant de pouvoir agir.

L'avis avait besoin qu'Aqua identifie la compromission. Le retrait avait besoin que PyPI identifie le paquet. La liste d'IoC avait besoin que quelqu'un lise scan.aquasecurtiy.org comme hostile plutôt que comme une coquille du nom d'un fournisseur en qui tout le monde a confiance. La checklist « avez-vous installé entre 10 h 39 et 16 h 00 UTC » avait besoin des bonnes cinq heures. La réputation de domaine avait besoin que le domaine ait une réputation. La réattribution qui a annoncé à 2 085 organisations qu'elles regardaient la mauvaise semaine avait besoin qu'une archive fuitée refasse surface et qu'une équipe de recherche s'y attelle, cinq mois plus tard.

Chacun de ces éléments est un contrôle d'identité : il agit sur une chose une fois que quelqu'un a nommé la chose. Nommer, c'est ainsi que l'écosystème partage la connaissance, et ça marche ; ce n'est donc pas un reproche adressé à ceux qui rédigent les avis. C'est une remarque sur l'ordre des opérations. Un contrôle d'identité arrive après la nomination, et dans cette campagne chaque artefact est resté en ligne de trois à douze heures. Les dix-huit minutes entre la publication et la première collecte répondent à la question de savoir si cet ordre était suffisant.

Les contrôles qui changent l'issue, ici, sont ceux qui n'ont pas besoin du nom.

Une horloge, pas un oracle

Bromure Agentic Coding fait passer chaque récupération de paquet par le proxy MITM côté hôte avant qu'un seul octet n'atteigne la VM, et la barrière d'âge est la seule couche chaîne d'approvisionnement active par défaut. Elle refuse toute version de paquet plus jeune qu'un seuil. Deux jours, dès la sortie de la boîte.

Elle n'effectue aucune recherche. Elle n'a aucune opinion sur le mainteneur, le publieur, la signature ou le nom. Elle lit l'horodatage de publication et applique un plancher, en partant du principe qu'une version fraîche est celle qui a le plus de chances d'avoir été détournée il y a une heure, et qu'attendre que cette fenêtre passe ne coûte presque rien à un développeur au travail.

Face à cette campagne, c'est tout le combat. LiteLLM 1.82.7 et 1.82.8 ont existé quarante minutes. Un plancher de deux jours signifie qu'elles n'ont jamais existé du tout depuis l'intérieur de la VM. Le proxy retire les versions trop fraîches des métadonnées du registre et réoriente latest et les autres dist-tags vers la version survivante la plus récente : pip install litellm et toutes les plages semver se résolvent donc en 1.82.6, sans erreur et sans rien qu'un agent puisse contourner. Demandez une version empoisonnée par épinglage exact et le garde-fou de récupération d'artefact renvoie un 451 dont le corps indique l'âge réel du paquet face au minimum requis. Pip l'affiche tel quel :

Bromure Supply-Chain Security blocked this request: pypi package
[email protected] published 34 minutes ago — policy requires 2 days minimum

La barrière couvre npm, PyPI, Cargo, RubyGems et Packagist avec des données de publication complètes. Pour pip, elle effectue une recherche à la demande contre l'API JSON de PyPI, puisque l'index PEP 503 par défaut ne porte aucun horodatage. Chaque décision atterrit dans le Journal de sécurité (Fenêtre → Journal de la chaîne d'approvisionnement…) au fil de l'eau.

CONTRÔLE D'IDENTITÉ — agit une fois le nom connuartefact publiét + 0on s'en aperçoitheures … moisattributionavis, IoCle contrôle bloquesecrets partis à t + 18 minréattribué à t + 5 moisPLANCHER TEMPOREL — agit au moment du fetchartefact publiét + 0l'agent résout la dépendancele proxy lit l'horodatageâge < 2 jours → non servimétadonnées réécrites, ou 451aucun nom requis,aucun avis requis
Un contrôle d'identité doit attendre que le monde nomme l'artefact. Un plancher temporel n'apprend jamais le nom et n'en a jamais besoin : il compare un horodatage de publication à un nombre que vous avez choisi à l'avance.

Ce que récupère un dump mémoire

Le meilleur coup du voleur fut de lire la mémoire du worker du runner CI pour récupérer les secrets que le masquage des logs avait cachés. Ça marche parce que le masquage est cosmétique et que le processus, lui, détient les valeurs.

Lancez-le contre un espace de travail Bromure et il récupère des leurres, parce que le processus détient des leurres. La frontière du fil est le mécanisme central du produit : chaque identifiant que vous configurez apparaît à l'intérieur de la VM sous forme de faux préservant la structure, tandis que la vraie valeur reste chiffrée sur votre Mac. Le proxy hôte la substitue sur le fil une fois la requête sortie de la VM, et uniquement lorsque la requête est destinée à l'hôte pour lequel cet identifiant a été forgé.

Passez la propre liste du voleur en revue contre une VM Bromure :

/proc/*/environ

ANTHROPIC_API_KEY vaut sk-ant-api03-brm-…. OPENAI_API_KEY vaut sk-brm-…. GH_TOKEN vaut ghp_ suivi de 36 caractères. Structure préservée, donc claude et gh les acceptent sans broncher, et ils ne valent rien pour quiconque d'autre.

/proc/<pid>/mem

Vider le processus récupère les mêmes leurres que ceux de l'environnement. Aucune copie privilégiée ne se cache plus profond en mémoire : le proxy hôte effectue la substitution en dehors de la VM, après le départ des octets.

Clés privées SSH

Il n'y en a aucune. SSH_AUTH_SOCK pointe vers un pont ssh-agent sur le port vsock 8444. Les octets de la clé privée n'entrent jamais dans l'invité et ne peuvent pas en être extraits. Bromure laisse votre agent de session macOS hors d'exposition, par conception.

Cloud, k8s, Docker, bases de données

~/.aws/config, ~/.kube/config, ~/.docker/config.json, ~/.git-credentials, ~/.config/doctl/config.yaml. Tous présents, tous remplis, tous faux : brm-k8s-…, brm-docker-…, brm-db-…, glpat- suivi de 20 caractères, dop_v1_ suivi d'hexadécimal.

Bromure dérive chaque faux à partir de la vraie valeur et d'un sel de 32 octets propre à l'installation, via HKDF-SHA256: un outil qui prend l'empreinte de sa propre clé (Claude Code met en cache un hachage de clé) ne voit donc jamais l'identifiant changer d'une session à l'autre. Et il n'y a pas d'interrupteur à oublier. Le proxy est l'unique route de la VM vers le réseau, donc une requête qui le contourne emporte un leurre et échoue à l'authentification en amont. La frontière échoue en position fermée, par construction.

Le typosquat que personne n'avait jamais vu

Passons à l'exfiltration. Le 19 mars, scan.aquasecurtiy.org n'avait aucune réputation, aucun historique et aucune raison de figurer sur la moindre liste de blocage. C'est pour cela que TeamPCP l'a choisi.

Le détecteur de compromission de Bromure ne lit rien de tout ça. Il surveille vos identifiants plutôt que la réputation de la destination. Un automate d'Aho-Corasick construit à partir des faux forgés pour l'espace de travail balaie chaque requête sortante, en-têtes et corps. Quand l'un de ces faux apparaît dans une requête destinée à un hôte hors du périmètre pour lequel il a été forgé, le proxy traite la requête comme une tentative d'exfiltration :

  1. Le proxy refuse la requête avec un HTTP 451 et ne transmet pas un seul octet à la destination.
  2. Bromure met la VM en pause sur-le-champ.
  3. Une alerte nomme ce qui s'est passé : une tentative sortante de faire fuiter un identifiant de session vers un hôte pour lequel il n'a pas été forgé.

Bromure marque ensuite l'espace de travail comme compromis, et le lancement suivant exige d'effacer l'image disque de la VM et le répertoire personnel persistant avant de démarrer. Vos jetons, vos clés SSH et vos réglages d'espace de travail survivent à cela. L'Inspecteur de traces (⇧⌘I) et bromure-cli trace leaks montrent l'hôte fautif et la requête exacte, de sorte que vous savez en une minute si une dépendance ou une instruction injectée par prompt en est responsable.

Un faux de forme AKIA cantonné à amazonaws.com, apparaissant dans un POST vers scan.aquasecurtiy.org, déclenche cet automate dès la première tentative. Le détecteur n'a jamais entendu parler de ce domaine et n'en a nul besoin. Il sait que cet identifiant a une seule famille de destinations légitimes, et que celle-ci en est une autre. Il n'y a rien à activer ; il tourne sur chaque requête.

La chronologie que vous détenez déjà

Le problème d'archivage, ici, survit à l'incident.

Les chercheurs ont reconstitué la chronologie depuis l'extérieur, à partir d'une archive fuitée de 153 Go qui a dû refaire surface, être obtenue, puis être tamisée avant que quiconque puisse dire quelle fenêtre comptait. Les victimes ne pouvaient pas répondre à partir de leurs propres traces, parce que ces traces omettaient les deux faits décisifs : quels artefacts leurs pipelines avaient récupérés et quand, et où les processus qui en résultaient avaient envoyé du trafic.

Un espace de travail Bromure produit cette trace comme effet secondaire de son fonctionnement. Chaque récupération traverse le proxy hôte, donc le Journal de sécurité conserve chaque décision de barrière d'âge, d'OSV, de socket.dev et de 451 en flux continu. Chaque requête traverse le même proxy, donc bromure-cli trace ls vous donne l'hôte, la méthode, le statut, la latence et les marqueurs swap×N / LEAK×N par espace de travail, trace hostnames liste chaque hôte distinct qu'une session a contacté, et trace summary agrège le tout. Le tout reste chiffré au repos sur votre Mac, sous la clé maîtresse du coffre.

Ainsi la question à laquelle SOCRadar a répondu en août, ai-je été collecté, et quand ?, devient une recherche de deux minutes que vous menez vous-même en mars, contre vos propres données, sans attendre que quiconque publie le bon nom.

Voilà l'argument. Personne ne peut rendre l'écosystème plus rapide à nommer les choses : mars a montré la réponse à incident du fournisseur lui-même laissant derrière elle une traîne de dix-neuf jours et une attribution erronée. Ce que vous pouvez changer, c'est de savoir si votre issue dépend du nom. Mettez une horloge devant le registre, mettez des leurres dans la machine qui exécute le code, et tenez votre propre journal de ce qu'elle a récupéré et de ce à quoi elle a parlé.


Sources : SecurityWeek, « Trivy, Not LiteLLM Behind the 2,500 Org Compromise » (14 août 2026) · Avis Aqua Security GHSA-69fq-xp46-6x23 · StepSecurity, « Trivy Compromised a Second Time » · The Hacker News (12 août 2026) · Help Net Security (13 août 2026) · CrowdStrike, « From Scanner to Stealer »