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.
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.
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 :
- Le proxy refuse la requête avec un HTTP 451 et ne transmet pas un seul octet à la destination.
- Bromure met la VM en pause sur-le-champ.
- 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 »