Context
Récemment, le système de surveillance sécurisée MistEye a détecté des informations concernant des versions anormales de plusieurs paquets npm appartenant à l'organisation Red Hat Cloud Services. Cette affaire concerne au total 32 paquets npm et 96 versions de cette organisation. Ce article sélectionne trois échantillons locaux pour une analyse approfondie hors ligne : ces échantillons ne sont pas des paquets imitant des espaces de noms ou des typosquatting, mais des versions réelles utilisant le scope @redhat-cloud-services ; l'analyse du code source des échantillons confirme qu'un chargeur malveillant multicouche, activé automatiquement lors de l'installation, a été intégré dans les tarballs.
Après restauration complète, il est possible de confirmer que les charges utiles de ces 3 échantillons possèdent les capacités suivantes : lecture de la mémoire du GitHub Actions Runner, collecte d’identifiants multi-cloud et locaux, exfiltration via l’API GitHub et dead-drop, injection dans les workflows GitHub, auto-propagation via npm, persistance via Claude Code / VS Code / systemd / LaunchAgent, contournement de Harden-Runner / StepSecurity, détection d’EDR et de produits de sécurité. Du point de vue de la portée des capacités, les entités potentiellement impactées incluent les machines des développeurs, les runners CI/CD, les conteneurs de build, les dépôts GitHub, les workflows GitHub Actions, la chaîne de publication npm et les identifiants d’environnement cloud ; la portée réelle de l’impact doit être confirmée en combinant les journaux d’installation, les audits de dépôts et les témoignages côté plateforme.
Du point de vue de la structure du code, des chemins de propagation et de la combinaison de capacités, ce logiciel malveillant est une variante de Shai-Hulud.
MistEye répond
MistEye est un système de veille sur les menaces Web3 et de surveillance de sécurité en temps réel, développé en interne par SlowMist, intégrant des capacités de surveillance sécurisée et d'agrégation d'intelligence, offrant aux utilisateurs des alertes de risque en temps réel et une protection de leurs actifs.
Après avoir capturé cet événement de poison de chaîne d'approvisionnement npm de Red Hat Cloud Services et ses échantillons malveillants associés, le système MistEye a déclenché une alerte critique et effectué une analyse systématique de la structure d'obfuscation, du déchiffrement de la charge utile, de la restitution des capacités et des IOC de cette chaîne d'attaque.
(https://enterprise.misteye.io/threat-intelligence/SM-2026-378450)
Aperçu de la chaîne d'attaque
La partie analyse technique de ce document repose uniquement sur une analyse locale, hors ligne et statique, consistant à décompresser, décoder et vérifier la désobfuscation de 3 échantillons npm tgz.
Les échantillons couverts par cette vérification sont les suivants :
@redhat-cloud-services/frontend-components-config
Version : 6.11.3
tgz SHA-256 : 0c9c67ec40d5f23efa1ec3470d0ac88b4993ccc0e92be913fc29a337dfc4f060
@redhat-cloud-services/types
Version : 3.6.1
tgz SHA-256 : d543bb3cdf1569c2b3d38c8a4081ed746cfe78bf3236c2302704d79ab7fa9558
@redhat-cloud-services/rule-components
Version : 4.7.2
tgz SHA-256 : aaf00d06baa3c679b82452c50014e9824b8874e9ca2d150f19095f8de19ba90f
Les trois échantillons partagent exactement le même point d'entrée d'attaque : package.json contient tous scripts.preinstall = "node index.js". Cela signifie que dès l'installation de ces versions sur un hôte développeur ou dans un environnement CI/CD, le fichier index.js à la racine est exécuté automatiquement pendant la phase d'installation, sans que l'utilisateur doive importer ou exécuter explicitement le code métier.
Trois échantillons partagent le même charge utile malveillante centrale, mais présentent des paramètres d’emballage externe différents. Autrement dit, les trois échantillons utilisent des valeurs de décalage ROT/César distinctes ainsi que des clés/IV/tag AES-GCM différentes ; cela rend difficile la réutilisation directe de caractéristiques statiques basées sur le hash de l’emballage externe, une clé fixe ou un décalage fixe. Cependant, le chargeur d’environnement d’exécution Bun déchiffré et le hash de la charge utile malveillante centrale sont exactement identiques : l’un est un module d’aide au démarrage destiné à préparer l’environnement d’exécution Bun, l’autre est la charge utile centrale contenant réellement les fonctionnalités malveillantes. Dans cet article, nous les désignons respectivement comme « chargeur d’environnement d’exécution Bun (nom de variable dans le code source _b) » et « charge utile malveillante centrale (nom de variable dans le code source _p) ».
La chaîne d'attaque restaurée est la suivante :

Analyse technique
1. Entrée : prise en charge des scripts npm via une injection de lifecycle
Les trois échantillons déclenchent la chaîne d'attaque via le hook de cycle de vie preinstall dans package.json :
"scripts": {
"preinstall": "node index.js"
}
preinstall s'exécute automatiquement pendant la phase d'installation de npm. Pour les composants frontend ordinaires, les définitions de types ou les paquets de composants de règles, il n'est généralement pas nécessaire d'exécuter un fichier JavaScript volumineux à la racine pendant la phase d'installation ; ceci constitue le signal d'anomalie le plus direct.
Les preuves au niveau de l'échantillon sont les suivantes :
@redhat-cloud-services/frontend-components-config@6.11.3
- principal : lib/index.js
- Champ files : ["/lib", "/bin"]
- Fichier racine index.js SHA-256 : 545a1838c66e1771f58d84a17b3e1841e5eeab91a73f4ccc59c9492450a6d9c0
@redhat-cloud-services/types@3.6.1
- principal : index.d.ts
- Champ files : non défini
- Fichier racine index.js SHA-256 : b86c5ae9e95bd841a595440faa3eb6317441e746f241ae8fd641ab59ed1d1966
@redhat-cloud-services/rule-components@4.7.2
- principal : index.js
- Champ files : non défini
- Fichier racine index.js SHA-256 : 1a30a9abe20bab121aaa75ed040565af14e6cdfb745609ee0e7b94a2d814fb9c
Dans ce cas, le champ files de frontend-components-config@6.11.3 déclare uniquement "/lib" et "/bin", mais un fichier index.js supplémentaire est présent à la racine du tarball et est appelé par preinstall. Cette anomalie ne s'applique qu'à cet échantillon ; types@3.6.1 et rule-components@4.7.2 n'ont pas défini le champ files, donc cette anomalie ne peut pas être généralisée à tous les échantillons.
2. Niveau 1 : tableau de chiffres + substitution de lettres ROT/César
Les trois fichiers index.js à la racine des échantillons sont des fichiers JavaScript en une seule ligne très volumineux, avec une structure identique et un corps constitué d'un wrapper try { eval(...) } catch (...) :
Ce wrapper convertit d'abord un grand tableau de nombres en chaîne de caractères à l'aide de String.fromCharCode, applique ensuite un décalage ROT/César aux lettres alphabétiques de la chaîne, puis passe le résultat décodé à eval pour exécution. La gestion des erreurs n'affiche que le préfixe "wrapper:", réduisant ainsi la visibilité des erreurs en cas d'échec d'installation.
Les valeurs de déplacement externe et le hash Stage 2 des trois échantillons sont les suivantes :
frontend-components-config@6.11.3
index.js taille : 4 294 798 octets
ROT/Caesar shift : 10
Étape 2 SHA-256 : b19c2fd48535c8c40aeb3e627ce92775f33ef9292611767bb1236c238e6f90cc
types@3.6.1
index.js taille : 4 135 588 octets
ROT/Caesar shift : 4
Étape 2 SHA-256 : 9c0425aa6e6d7792ac38d24f3e7245f42fcaa553ddfeb6bd97677017f10c3b75
rule-components@4.7.2
index.js taille : 4 294 336 octets
ROT/Caesar shift : 11
Étape 2 SHA-256 : d590bd375d95e4ac072b7ebc1fc4489bcaf5f20a939e92486267aa398bcf1e5d
3. Deuxième couche : Déchiffrement AES-128-GCM du chargeur de démarrage Bun et de la charge utile principale
Le code de la Stage 2, décodé via ROT/Caesar, effectue un déchiffrement AES-128-GCM (Advanced Encryption Standard - Galois/Counter Mode) à l'aide de Node.js crypto.createDecipheriv et définit le tag d'authentification via setAuthTag. La cible du déchiffrement est deux composants suivants : le lanceur de l'environnement d'exécution Bun et le charge utile malveillant principal.
Parmi ceux-ci, le chargeur d'environnement Bun est chargé de détecter, localiser ou préparer l'environnement d'exécution Bun afin que le code suivant puisse être exécuté via Bun ; le chargeut malveillant principal constitue le cœur de la chaîne d'attaque et contient les principales capacités malveillantes, telles que la collecte d'identifiants, la propagation via GitHub/npm, la persistance, la contournement des protections et le déchiffrement des ressources intégrées.
Chacun des trois échantillons a utilisé une clé AES-128-GCM, un vecteur d’initialisation et un tag d’authentification différents pour chiffrer ces deux composants, mais après déchiffrement, les empreintes SHA-256 des trois bootstraps d’environnement Bun sont toutes ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6, et les empreintes SHA-256 des trois charges utiles malveillantes principales sont toutes 0dc06ecdaa63fe24859cfd955053c23245c536e4733480239d14bebf12688e35. Cela indique que l’attaquant a modifié les paramètres d’emballage externe dans différents paquets npm, tout en réutilisant les mêmes composants malveillants principaux.
Après déchiffrement, Stage 2 écrit la charge utile malveillante principale dans /tmp, l'exécute via Bun, puis supprime le fichier temporaire. La logique réelle reconstituée à partir des trois échantillons est la suivante :
Ainsi, le modèle de fichier temporaire vérifiable dans les trois échantillons est :
/tmp/p.js
L'initialiseur de l'environnement Bun mesure 898 octets, et les trois hachages d'échantillons sont identiques :
ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6
L'initialiseur d'environnement Bun contient des logiques telles que child_process.execSync, fs.existsSync, fs.mkdtempSync, fs.chmodSync, os.platform(), os.arch() et getBunPath() pour localiser ou préparer l'environnement d'exécution Bun. Autrement dit, la Stage 2 ne suppose pas simplement que Bun est installé globalement sur le système ; en dehors du chemin d'exécution Bun, il charge d'abord l'initialiseur d'environnement Bun, puis appelle Bun via getBunPath() pour exécuter la charge utile malveillante principale.
4. Troisième couche : tableau de chaînes obfuscator.io
Le charge utile malveillant déchiffré n'est pas du JavaScript en clair lisible directement, mais est encore obscurci selon le style de obfuscator.io avec une table de chaînes. Cette couche regroupe un grand nombre de chaînes dans un tableau et les restaure à l'exécution grâce à une indexation dynamique et une logique de rotation, empêchant les analystes de récupérer directement les API, chemins et noms de configuration critiques tant que la table de chaînes n'a pas été entièrement restaurée.
5. Niveau 4 : B5 Chiffrement de chaîne personnalisé
Après déobfuscation de la table de chaînes obfuscator.io, une couche supplémentaire de chiffrement de chaînes personnalisé B5 est présente dans le charge utile malveillant principal. Les paramètres suivants ont été confirmés :
- KDF : PBKDF2 (Password-Based Key Derivation Function 2)
- Fonction de hachage : SHA-256
- Nombre d'itérations : 200000
- Longueur de la clé : 32 octets
- Nombre de tours de déchiffrement : 3
- mot de passe : ba2c6ddb3672bdd6a611e6850b4f700b52aed3dab2f1b3d5f8c839d4a157a709
- salt : 5b26508dc0f1075a7c0b4d8aa464487e
Le résultat déchiffré est le suivant :
Parmi ceux-ci, B5 déchiffré révèle plusieurs chaînes de texte clair critiques. Il convient de noter que certaines chaînes étaient à l'origine dans la table de chaînes de obfuscator.io et ne deviennent directement grepables qu'après avoir effectué les deux étapes : « remplacement de la table de chaînes + déchiffrement B5 » :
6. Couche d’embarquement supplémentaire : AES-256-GCM + gzip
La charge utile malveillante principale contient également des ressources intégrées chiffrées avec AES-256-GCM et compressées avec gzip. La logique de décompression utilise :
Les ressources intégrées résolues sont classées en trois catégories selon leur fonction :
Lecture de mémoire interplateforme :
Ces ressources sont utilisées pour lire la mémoire des processus en cours d'exécution. Les clés et jetons dans les fichiers ordinaires peuvent être découverts en examinant les fichiers, mais les exécutables CI/CD ou les outils de développement stockent temporairement certaines valeurs sensibles en mémoire pendant leur exécution. Les attaquants intègrent des scripts de lecture de mémoire pour les plateformes Linux, Windows et macOS afin de capturer ces secrets temporaires présents en mémoire sur différents systèmes.

Configuration persistante :
Ces ressources sont utilisées pour « laisser des points de déclenchement arrière ». Autrement dit, même après la fin du processus d'installation npm initial, les attaquants souhaitent que le code malveillant s'exécute à nouveau lorsque le développeur ouvre ultérieurement le projet, démarre l'éditeur, entre dans une session Claude Code ou se reconnecte au système. Ces ressources ciblent donc les configurations au niveau du projet et les mécanismes de démarrage automatique au niveau utilisateur : les configurations au niveau du projet sont plus discrètes, tandis que les services système conviennent mieux à une exécution à long terme.

C2 et propagation :
Ces ressources intégrées sont utilisées pour le transfert ultérieur des chaînes de commandes et des secrets de dépôt. YZ, une fois déchiffré, est un moniteur de recherche de commit GitHub qui recherche périodiquement des messages de commit au format firedalazer ., vérifie la signature, puis télécharge et exécute le contenu Python distant ; zZ, une fois déchiffré, est un modèle de workflow GitHub Actions nommé Run Copilot, qui écrit ${{ toJSON(secrets) }} dans un fichier format-results.txt et le télécharge en tant qu'artifact. Le premier fournit la chaîne d'obtention et d'exécution des tâches ultérieures, le second fournit un modèle pour le transfert des secrets du dépôt via les artifacts GitHub Actions ; l'injection réelle du workflow, l'attente d'exécution et le téléchargement de l'artifact sont gérés par la logique API GitHub associée incluse dans la charge principale.

Analyse des capacités de la charge utile malveillante principale
Après déobfuscation en plusieurs étapes, l'échantillon libère et exécute finalement le charge utile malveillant principal. Toutes les analyses suivantes portent sur le module de charge utile principale, décomposé en cinq étapes : collecte de données, exfiltration, propagation, persistance et contournement.
Lecture de mémoire du runner GitHub Actions :
L'échantillon intègre des scripts de lecture de mémoire pour les plateformes Linux, Windows et macOS, offrant une capacité de dump cross-platform. Sous Linux, l'implémentation effectue l'extraction de mémoire en lisant les fichiers /proc//maps et /proc//mem du processus cible :
La logique de déclenchement actif du charge utile malveillant cible le runner Linux de GitHub Actions : lorsque GITHUB_ACTIONS === "true" et RUNNER_OS === "Linux", le code recherche le processus Runner.Worker, effectue un dump de la mémoire, puis extrait les secrets masqués au format "":{"value":"","isSecret":true}. Les chemins d'acquisition actifs confirmés dans les trois échantillons se concentrent sur le runner Linux, bien que les scripts pour les trois plateformes soient tous intégrés.
2. Collecte des identifiants locaux des développeurs et cloud multiple :
Le charge utile malveillant principal inclut un module systématique de collecte d'identifiants, couvrant les fournisseurs cloud, les environnements CI, les configurations locales des développeurs, l'interface CLI de GitHub, les gestionnaires de mots de passe et les fichiers de portefeuilles.
Les objectifs de collecte des identifiants du fournisseur de cloud sont les suivants :

Le module AWS contient également la logique liée à ECS / IMDS (Instance Metadata Service) / STS WebIdentity.
La collecte des identifiants locaux des développeurs couvre les objectifs suivants :

La logique du gestionnaire de mots de passe est implémentée dans le code source sous forme de tableau runCommand(command, args), et non par concaténation de chaînes shell.
Le module de collecte d'identifiants couvre une large gamme de cibles, allant des plateformes cloud aux environnements locaux des développeurs ; les données sensibles collectées sont exfiltrées via les mécanismes suivants.
3. API GitHub hors contexte et dead-drop :
Le charge utile malveillant principal utilise l'API GitHub comme canal principal d'exfiltration de données. Les requêtes falsifient l'User-Agent en tant que python-requests/2.31.0 et, après validation du token via x-oauth-scopes (qui doit inclure repo, public_repo ou workflow), exécutent l'exfiltration : création d'un dépôt (la description est fixée à Miasma: The Spreading Blight), puis écriture des données volées, encodées en base64, via PUT /repos///contents/results/.
De plus, la charge utile implémente un mécanisme dead-drop basé sur la recherche de commits GitHub, en recherchant le marqueur "thebeautifulmarchoftime" (ou "thebeautifulsnadsoftime") pour obtenir les instructions C2 :
let result = await X9("thebeautifulmarchoftime ", xZ);
La ressource intégrée YZ.bin est un moniteur de commit GitHub indépendant qui interroge toutes les 3600 secondes à la recherche de messages de commit au format firedalazer. Après vérification de la signature, il télécharge et exécute le contenu Python distant. Cela constitue une chaîne de commande secondaire indépendante du canal principal de diffusion.
Le code source contient également une configuration d'expéditeur HTTP POST ciblant les points de terminaison api.anthropic.com et v1/api. Le champ noop de cette configuration est défini sur true, ce qui signifie qu'en l'absence de modification externe, cet expéditeur n'initiera aucune requête réseau réelle. Par conséquent, l'analyse statique ne peut que confirmer l'existence de cette structure de code, sans prouver la présence d'une transmission active vers l'API Anthropic.
4. Répertoire GitHub et infection du workflow :
La charge utile malveillante principale peut manipuler les objets git du dépôt via l'API REST / GraphQL de GitHub :

Un workflow malveillant construit sous le nom de « release » demande l'autorisation id-token: write et utilise le chemin OIDC (OpenID Connect) pour échanger contre un jeton de publication npm. Les variables d'environnement et l'interface du chemin OIDC sont ACTIONS_ID_TOKEN_REQUEST_TOKEN et ACTIONS_ID_TOKEN_REQUEST_URL, avec l'audience définie sur npm:registry.npmjs.org. Au niveau du code source, il est confirmé que le payload est capable d'injecter un workflow dans un dépôt GitHub et d'exploiter OIDC / la publication approuvée npm pour obtenir des droits de publication.
Outre la méthode d'injection du workflow via les git refs mentionnée ci-dessus, la charge utile intègre un mécanisme plus subtil de transfert de secrets : un workflow GitHub Actions se faisant passer pour Run Copilot écrit ${{ toJSON(secrets) }} dans format-results.txt et télécharge l'artefact. Contrairement à l'écriture directe des secrets dans le contenu du dépôt, cette approche privilégie le transfert de données via les artefacts générés par l'exécution du workflow, ce qui peut échapper à une revue classique des différences de code.
5. Exécuter le workflow Copilot via l'artifact pour transférer les secrets :
La ressource intégrée zZ.bin est un workflow GitHub Actions se faisant passer pour Copilot, qui écrit ${{ toJSON(secrets) }} dans format-results.txt et le télécharge en tant qu'artefact.
Les comportements associés de l'API GitHub incluent : créer/mettre à jour une branche temporaire → écrire le blob du workflow → attendre l'exécution du workflow → télécharger l'artifact zip → lire format-results.txt → supprimer l'exécution du workflow et la branche temporaire. Les mots-clés de recherche associés incluent Run Copilot, VARIABLE_STORE, format-results, chore/add-codeql-static-analysis, .github/workflows/codeql.yml.
6. Auto-propagation de npm :
Le charge utile malveillant principal dispose de deux chemins de publication / propagation npm indépendants.
Chemin du jeton npm : Le chargement énumère les jetons npm de l'utilisateur actuel via GET /-/npm/v1/tokens, filtre les jetons dont bypass_2fa === true et qui possèdent des droits d'écriture sur le package, puis utilise ces jetons pour effectuer une publication automatisée. bypass_2fa est un champ de métadonnées du jeton, et non un paramètre du corps de publication.
Chemin GitHub Actions OIDC : lit les variables d'environnement ACTIONS_ID_TOKEN_REQUEST_TOKEN et ACTIONS_ID_TOKEN_REQUEST_URL, demande un jeton OIDC avec l'audience npm:registry.npmjs.org, et échange via l'interface d'échange de jeton OIDC npm pour obtenir des autorisations de publication.
Le processus de contamination du tarball consiste à décompresser → écrire le fichier malveillant index.js → modifier package.json :
Deux types d'entrées sont distingués : les trois échantillons téléchargés localement utilisent preinstall : "node index.js" — dans ce cas, l'environnement d'installation n'exige pas l'installation préalable de Bun, et l'utilisation de Node.js comme entrée initiale est plus discrète ; lors de la propagation autonome qui pollue d'autres packages, on insère preinstall : "bun run index.js" et on ajoute une dépendance à bun — car les logiques ultérieures du charge utile malveillant dépendent des API fournies par l'exécuteur Bun (telles que Bun.gunzipSync et Bun.file), et dans les scénarios de propagation autonome, il est essentiel de garantir que l'environnement cible dispose déjà de Bun.
7. Mécanisme de persistance :
Le charge malveillant principal inclut quatre méthodes de persistance, couvrant le répertoire du projet et le démarrage au niveau système.
Claude Code SessionStart hook : Le fichier ressource S8.bin écrit le SessionStart hook dans .claude/settings.json, exécutant automatiquement node .claude/setup.mjs à chaque démarrage de session Claude Code. Un charge utile malveillante est également écrite dans .claude/setup.mjs et .claude/index.js (le chemin est construit par ".claude/" + M0, où M0 = "index.js").
Tâche folderOpen de VS Code : intégrer la ressource EZ.bin pour écrire la tâche folderOpen dans .vscode/tasks.json et exécuter automatiquement le même script lors de l'ouverture du dossier du projet infecté. Écrire également dans .vscode/setup.mjs.
kitty-monitor (systemd / LaunchAgent) : intègre la ressource RZ.bin pour installer un service persistant, avec des fichiers répartis dans /.local/share/kitty/cat.py, /.config/systemd/user/kitty-monitor.service (Linux) et ~/Library/LaunchAgents/com.user.kitty-monitor.plist (macOS). Le chargeur malveillant crée le répertoire /tmp/kitty-, écrit YZ.bin (moniteur de commit GitHub) dans cat.py, puis l'installe via RZ.bin.
gh-token-monitor (systemd / LaunchAgent) : intègre la ressource jZ.bin pour installer le service de surveillance des tokens GitHub, avec les fichiers répartis dans les répertoires /.local/bin/gh-token-monitor.sh et /.config/gh-token-monitor/, ainsi que les chemins systemd / LaunchAgent correspondants. Ce monitor détecte si un token GitHub retourne un code d'état 40x ou si les métadonnées du package npm @cap-js/openapi@1.4.1 sont anormales, et déclenche le gestionnaire :
Cela signifie que lorsque le monitor détecte un code d'état 40x renvoyé par l'API GitHub, par exemple en cas de révocation, d'expiration ou d'anomalie de permission du token, le script malveillant exécute le handler fourni. Les handlers récupérés à partir des trois échantillons de cet article sont : rm -rf ~/; rm -rf ~/Documents. Lors du traitement, il ne faut pas révoquer directement le token sur un hôte susceptible d'exécuter encore le payload — il faut d'abord isoler l'hôte, supprimer les mécanismes de persistance et les processus en cours, puis renouveler les identifiants depuis un environnement propre.
8. Harden-Runner / StepSecurity :
Le charge utile malveillant principal intègre une logique de détection et de destruction ciblant StepSecurity Harden-Runner, contournant les protections CI/CD par les étapes suivantes :
- Vérifiez si la socket Docker /var/run/docker.sock est disponible ;
- Lister les conteneurs Docker en recherchant les mots-clés harden-runner / stepsecurity dans le nom ou l'image ;
- Arrêtez le conteneur correspondant via POST /containers//kill ;
- Créer un conteneur Alpine privilégié et remplacer le fichier sudoers par runner ALL=(ALL) NOPASSWD:ALL ;
- Écrire des résolutions invalides pour les domaines liés à StepSecurity dans /etc/hosts ;
- Remplacer /etc/resolv.conf par 8.8.8.8 et 1.1.1.1.
Cette logique d'attaque indique que l'échantillon a été spécifiquement adapté aux environnements CI/CD protégés par StepSecurity.
9. EDR / Détection de produits de sécurité, évaluation de l’environnement et contournement :
Le charge utile malveillant principal détecte les noms de processus et les chemins d'installation de plusieurs produits EDR (Endpoint Detection and Response) / sécurité :

La charge contient également une logique d'évitement régional : détecter les variables d'environnement LC_ALL, LC_MESSAGES, LANGUAGE, LANG ; après les avoir converties en minuscules, ignorer l'exécution si elles commencent par « ru ». La reconnaissance des environnements CI prend en charge GitHub Actions, GitLab CI, Travis CI, CircleCI, Jenkins, AWS CodeBuild, Buildkite, AppVeyor, Bitbucket, Drone, TeamCity, Cirrus CI, etc. Les indicateurs d'état utilisés pendant l'exécution incluent tmp.0987654321.lock, __IS_DAEMON (marqueur pour les sous-processus daemon détachés), SKIP_DOMAIN (ignorer le chemin de l'expéditeur de domaine), /tmp/kitty-*, cat.py et /var/tmp/.gh_update_state.
Analyse d'impact
Du point de vue des capacités du code source, l'impact de ces trois échantillons ne se limite pas à une exécution unique lors de la phase d'installation npm. Leur risque réel peut être divisé en quatre niveaux :
Au niveau de l'hôte du développeur. Les échantillons collectent les variables d'environnement, .npmrc, .pypirc, clés SSH, configuration Docker, .env, jeton GitHub CLI, données du gestionnaire de mots de passe et fichiers de portefeuille, et maintiennent une capacité de déclenchement ultérieur via Claude Code, VS Code, un service utilisateur systemd ou un LaunchAgent macOS.
Au niveau du runner CI/CD. L'échantillon identifie le runner Linux de GitHub Actions, lit la mémoire du processus Runner.Worker et extrait les secrets masqués ; il intègre également des logiques de contournement de StepSecurity Harden-Runner pour tenter de détruire ou de contourner les composants de protection CI/CD.
Au niveau de l'organisation et du dépôt GitHub. Les échantillons peuvent utiliser l'API GitHub pour créer des dépôts, écrire dans contents/results/, manipuler les refs/blobs/trees/commits git, injecter un workflow malveillant et transférer des secrets via l'exécution du workflow Copilot + artefact.
Au niveau de la propagation de l'écosystème npm. Les jetons npm pouvant être ciblés sont ceux ayant les droits d'écriture sur le package et dont bypass_2fa === true ; il est également possible d'exploiter le chemin GitHub Actions OIDC / npm trusted publishing pour obtenir des droits de publication ; ensuite, télécharger le tarball cible, y insérer un chargeur malveillant, modifier preinstall, ajouter une dépendance Bun, augmenter la version patch et publier, créant ainsi une chaîne de propagation autonome sur npm.
Résumé
Les preuves de code source de trois échantillons montrent que ce paquet malveillant n'est pas simplement un script de vol de données au moment de l'installation, mais une combinaison d'un chargeur multistade et d'un implant complet : la couche externe est aléatoire par paquet, tandis que le gestionnaire d'environnement d'exécution Bun et le chargeur malveillant principal restent cohérents ; l'implant principal couvre plusieurs étapes, notamment la collecte d'identifiants, l'extraction de secrets CI, la propagation via GitHub/npm, la persistance et la contournement des mesures de protection.
Conception d'entrée hautement discrète. L'attaquant n'injecte pas de logique malveillante dans le code métier, mais monte un loader obscurci dans les scripts de cycle de vie npm. Ce loader est principalement chargé de déchiffrer et d'exécuter la charge utile intégrée, rendant difficile la détection des capacités réelles par simple revue du code source métier.
Chaîne de déobfuscation emboîtée en plusieurs niveaux. La charge malveillante passe par cinq couches d’emballage : un tableau de chiffres + substitution ROT, chiffrement AES-128-GCM, obfuscation via obfuscator.io, chiffrement de chaîne personnalisé B5, et une couche d’embarquement AES-256-GCM + gzip. Les clés ou paramètres de chaque couche peuvent varier indépendamment entre les paquets, rendant la détection par lots basée sur des caractéristiques statiques plus difficile.
Capacité d'attaque organisationnelle complète. Ce implant possède des fonctionnalités telles que la lecture de la mémoire des GitHub Actions Runner, la collecte d'identifiants multi-cloud et locaux, l'exfiltration via l'API GitHub et les dead-drop, l'infection des dépôts GitHub et des workflows, la propagation autonome via npm, ainsi que la persistance et la contournement des mesures de protection. D'après la structure du code source, il est déjà capable de déclencher une propagation depuis un point d'installation vers les dépôts, les chaînes CI/CD et les publications npm ; la portée réelle de la propagation doit être confirmée en combinant les journaux d'installation, les audits de dépôts et les témoignages côté plateforme.
Les preuves issues du code source confirment que les trois échantillons ont été déclenchés automatiquement via preinstall et ont déchiffré et exécuté le même implant principal. Toutefois, il n'est pas possible de démontrer uniquement à partir de ces trois échantillons tgz comment les permissions initiales ont été obtenues dans l'événement réel.
Recommandations de traitement
- Identifier et supprimer les versions malveillantes dans les dépendances du projet, le fichier lock, le cache du registre privé et le cache de construction.
- Vérifiez si les éléments suivants apparaissent dans les journaux d'installation : preinstall, node index.js, bun run, /tmp/p*.js, tmp.0987654321.lock.
- Ne révoquez pas directement le token sur l'hôte victime s'il est encore susceptible d'exécuter le payload. Il est recommandé d'abord d'isoler l'hôte victime, de nettoyer les processus en cours et les éléments de persistance, puis de renouveler les tokens liés à GitHub, npm, les identifiants cloud, Kubernetes, Vault, SSH, le registre Docker et le gestionnaire de mots de passe depuis un environnement propre.
- Vérifiez les récents branches, commits, workflows, artefacts et dépôts créés sur GitHub, en mettant l'accent sur les mots-clés tels que Run Copilot, format-results, chore/add-codeql-static-analysis, .github/workflows/codeql.yml, OIDC_PACKAGES.
- Vérifiez si le répertoire du projet a été modifié ou si de nouveaux fichiers ont été ajoutés : .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, .vscode/setup.mjs.
- Vérifiez la persistance au niveau utilisateur : /.local/share/kitty/cat.py, /.config/systemd/user/kitty-monitor.service, ~/Library/LaunchAgents/com.user.kitty-monitor.plist, fichiers associés à gh-token-monitor.
- Vérifiez l'historique de publication npm pour confirmer la présence d'une publication non autorisée de version patch ; effectuez en parallèle une audit des métadonnées du token npm, en mettant l'accent sur les tokens permettant de contourner l'authentification à deux facteurs et disposant des droits d'écriture sur le package.
- Effectuer une vérification d'intégrité des artefacts en aval construits dans un environnement contaminé.
IOC
Fichier malveillant
fichier : redhat-cloud-services-frontend-components-config-6.11.3.tgz MD5 : 633ad8849a59e2bfb7a0fe589e816a07 SHA1 : 675294612f455fe6a9acb195f0cbe3687d8e2e34 SHA256 : 0c9c67ec40d5f23efa1ec3470d0ac88b4993ccc0e92be913fc29a337dfc4f060
fichier : redhat-cloud-services-types-3.6.1.tgz MD5 : 9e6c5af01438b52c9a411686c1f1b8ff SHA1 : 88d098c8d96e9ae17550e9798c3b62c420464b8c SHA256 : d543bb3cdf1569c2b3d38c8a4081ed746cfe78bf3236c2302704d79ab7fa9558
nom de fichier : redhat-cloud-services-rule-components-4.7.2.tgz MD5 : f1ffdbf5e639899f26a6ebab2eec408d SHA1 : f3c5c21274045ae02fef11e931de6dcf8462a067 SHA256 : aaf00d06baa3c679b82452c50014e9824b8874e9ca2d150f19095f8de19ba90f
SHA256
ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6
0dc06ecdaa63fe24859cfd955053c23245c536e4733480239d14bebf12688e35
Dépendance malveillante
npm:@redhat-cloud-services/topological-inventory-client@3.0.10
npm:@redhat-cloud-services/topological-inventory-client@3.0.11
npm:@redhat-cloud-services/topological-inventory-client@3.0.13
npm:@redhat-cloud-services/compliance-client@4.0.3
npm:@redhat-cloud-services/compliance-client@4.0.4
npm:@redhat-cloud-services/compliance-client@4.0.6
npm:@redhat-cloud-services/rbac-client@9.0.3
npm:@redhat-cloud-services/rbac-client@9.0.4
npm:@redhat-cloud-services/rbac-client@9.0.6
npm:@redhat-cloud-services/insights-client@4.0.4
npm:@redhat-cloud-services/insights-client@4.0.5
npm:@redhat-cloud-services/insights-client@4.0.7
npm:@redhat-cloud-services/frontend-components@7.7.2
npm:@redhat-cloud-services/frontend-components@7.7.3
npm:@redhat-cloud-services/frontend-components@7.7.5
npm:@redhat-cloud-services/frontend-components-utilities@7.4.1
npm:@redhat-cloud-services/frontend-components-utilities@7.4.2
npm:@redhat-cloud-services/frontend-components-utilities@7.4.4
npm:@redhat-cloud-services/remediations-client@4.0.4
npm:@redhat-cloud-services/remediations-client@4.0.5
npm:@redhat-cloud-services/remediations-client@4.0.7
npm:@redhat-cloud-services/frontend-components-notifications@6.9.2
npm:@redhat-cloud-services/frontend-components-notifications@6.9.3
npm:@redhat-cloud-services/frontend-components-notifications@6.9.5
npm:@redhat-cloud-services/patch-client@4.0.4
npm:@redhat-cloud-services/patch-client@4.0.5
npm:@redhat-cloud-services/patch-client@4.0.7
npm:@redhat-cloud-services/host-inventory-client@5.0.3
npm:@redhat-cloud-services/host-inventory-client@5.0.4
npm:@redhat-cloud-services/host-inventory-client@5.0.6
npm:@redhat-cloud-services/rule-components@4.7.2
npm:@redhat-cloud-services/rule-components@4.7.3
npm:@redhat-cloud-services/rule-components@4.7.5
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.2
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.4
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.6
npm:@redhat-cloud-services/notifications-client@6.1.4
npm:@redhat-cloud-services/notifications-client@6.1.5
npm:@redhat-cloud-services/notifications-client@6.1.7
npm:@redhat-cloud-services/sources-client@3.0.10
npm:@redhat-cloud-services/sources-client@3.0.11
npm:@redhat-cloud-services/sources-client@3.0.13
npm:@redhat-cloud-services/integrations-client@6.0.4
npm:@redhat-cloud-services/integrations-client@6.0.5
npm:@redhat-cloud-services/integrations-client@6.0.7
npm:@redhat-cloud-services/frontend-components-config@6.11.3
npm:@redhat-cloud-services/frontend-components-config@6.11.4
npm:@redhat-cloud-services/frontend-components-config@6.11.6
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.2
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.3
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.5
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.1
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.2
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.4
npm:@redhat-cloud-services/frontend-components-remediations@4.9.2
npm:@redhat-cloud-services/frontend-components-remediations@4.9.3
npm:@redhat-cloud-services/frontend-components-remediations@4.9.5
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.1
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.2
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.4
npm:@redhat-cloud-services/javascript-clients-shared@2.0.8
npm:@redhat-cloud-services/javascript-clients-shared@2.0.9
npm:@redhat-cloud-services/javascript-clients-shared@2.0.11
npm:@redhat-cloud-services/quickstarts-client@4.0.11
npm:@redhat-cloud-services/quickstarts-client@4.0.12
npm:@redhat-cloud-services/quickstarts-client@4.0.14
npm:@redhat-cloud-services/config-manager-client@5.0.4
npm:@redhat-cloud-services/config-manager-client@5.0.5
npm:@redhat-cloud-services/config-manager-client@5.0.7
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.1
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.2
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.4
npm:@redhat-cloud-services/entitlements-client@4.0.11
npm:@redhat-cloud-services/entitlements-client@4.0.12
npm:@redhat-cloud-services/entitlements-client@4.0.14
npm:@redhat-cloud-services/tsc-transform-imports@1.2.2
npm:@redhat-cloud-services/tsc-transform-imports@1.2.4
npm:@redhat-cloud-services/tsc-transform-imports@1.2.6
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.1
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.2
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.4
npm:@redhat-cloud-services/frontend-components-testing@1.2.1
npm:@redhat-cloud-services/frontend-components-testing@1.2.2
npm:@redhat-cloud-services/frontend-components-testing@1.2.4
npm:@redhat-cloud-services/types@3.6.1
npm:@redhat-cloud-services/types@3.6.2
npm:@redhat-cloud-services/types@3.6.4
npm:@redhat-cloud-services/chrome@2.3.1
npm:@redhat-cloud-services/chrome@2.3.2
npm:@redhat-cloud-services/chrome@2.3.4
npm:@redhat-cloud-services/frontend-components-translations@4.4.1
npm:@redhat-cloud-services/frontend-components-translations@4.4.2
npm:@redhat-cloud-services/frontend-components-translations@4.4.4
npm:@redhat-cloud-services/vulnerabilities-client@2.1.8
npm:@redhat-cloud-services/vulnerabilities-client@2.1.9
npm:@redhat-cloud-services/vulnerabilities-client@2.1.11
