Une illustration abstraite générée sur un fond bleu marine sombre : une pile de cinq fines barquettes de mémoire cyan vues en coupe, chacune contenant des rangées de petits blocs gris, avec un petit cube ambre glissé entre la troisième et la quatrième barquette ; depuis ce cube, un fin filament ambré de particules décrit un arc à travers un vide sombre vers trois petits nœuds rouge-orangé disposés en triangle. Derrière les barquettes, des étiquettes grises ternes pendent à de fines boucles d'un anneau porte-clés. Aucun texte, aucune personne ni logo d'organisation n'apparaît. Le logo OrcaRouter est intégré dans le coin inférieur droit.
AI Safety Incidents

sckit : les packages de mémoire IA de MemTensor ont été piégés pour voler les identifiants et les prompts des agents

Auteur

Rowan Sterling

Date de publication

Derniers modèles · 20Voir tous les modèles →
Benchmarks : Artificial Analysis · mis à jour quotidiennement
Retour à tous les articles

Quatre versions de MemTensor publiées le 23 septembre 2026 transportaient le même implant Go allégé, et chacune était la version la plus récente sur son registre tant qu’elle était en ligne. Le paquet npm @memtensor/memos-cloud-openclaw-plugin est sorti en 0.1.21 à 02:23 UTC, en 0.1.23 à 03:49 et en 0.1.25 à 04:36 ; sur PyPI, MemoryOS est sorti en 2.0.34 à 05:25. Les quatre embarquaient sckit, qui démarre au lancement de la passerelle d’agent OpenClaw, redémarre à chaque rappel de mémoire — le texte d’invite de l’utilisateur étant fourni dans une variable d’environnement — et, dans le paquet Python, démarre lors d’un simple import du module memos. Il parcourt ensuite le répertoire personnel à la recherche de secrets npm, PyPI, GitHub, GitLab, AWS, Vault et SSH et envoie ce qu’il trouve à des serveurs sous skyleen[.]fr. Socket, StepSecurity, SafeDep et Aikido ont documenté la compromission indépendamment le jour même, et aucun n’a signalé de preuve que l’implant ait été exécuté sur un hôte réel. L’Orca AI Incident Archive conserve cette entrée dans son intégralité, aux côtés de tous les autres incidents de chaîne d’approvisionnement qu’elle a consignés, et chaque enregistrement porte sa propre note de confiance et son statut de préjudice.

Quatre versions, et un rapport de bug public au milieu de celles-ci

MemOS est le framework de mémoire open source de MemTensor pour les agents LLM, et le package npm est le connecteur qui permet à un runtime d'agent OpenClaw de lire et d'écrire dans un service de mémoire hébergé ; StepSecurity note que ce même package déclare aussi des points d'intégration pour les runtimes Clawdbot et Moltbot. Socket a mesuré le dépôt à environ 11 500 étoiles et 1 100 forks au moment de sa publication. Cette position explique pourquoi la compromission a fait l'objet de quatre enquêtes distinctes : comme l'a formulé StepSecurity, cela « place le plugin à l'intérieur d'un processus qui traite régulièrement des entrées utilisateur et peut hériter d'identifiants précieux ». Les personnes exposées sont celles qui ont installé l'un ou l'autre package — des développeurs sur leurs propres machines, ainsi que des runners CI et des conteneurs qui n'ont fait qu'exécuter une suite de tests.

La séquence npm est la partie la plus claire du dossier, car les horodatages du registre sont publics et plusieurs sociétés les ont relevés indépendamment. L’analyse de Socket et la chronologie de SafeDep concordent à la minute près : 0.1.21 à 02:23:04, 0.1.22 à 03:45:44, 0.1.23 à 03:49:20, 0.1.24 à 04:33:30 et 0.1.25 à 04:36:58. Les versions 0.1.22 et 0.1.24 étaient saines — Socket indique qu’elles correspondaient à la 0.1.20, hormis les chaînes de version — et la charge utile est revenue trois minutes et demie après chacune d’elles.

Le bond de taille est révélateur. Socket a mesuré la version 0.1.20 à environ 272 Ko décompressée et la version 0.1.21 à 43,6 Mo, car les versions malveillantes embarquaient six binaires de plateforme liés statiquement d'environ 7,4 Mo chacun. PyPI montre le même schéma : le wheel 2.0.34 faisait 19 201 772 octets contre 951 210 pour la version 2.0.33, selon Socket.

À 04:17 UTC — seize minutes avant la version propre 0.1.24 et dix-neuf avant que la charge utile ne réapparaisse en 0.1.25 — un utilisateur GitHub nommé omeridel a ouvert le ticket #173 sur le dépôt du plugin, intitulé « Versions malveillantes 0.1.21 et 0.1.23 publiées sur npm — le jeton de publication semble compromis ». Le ticket indique que 0.1.23 était alors le dernier tag, qu’aucune des deux versions ne correspondait à un commit ni à un tag du dépôt, et que le commit légitime le plus récent datait du 14 août. Il reste ouvert, sans commentaire des mainteneurs, à la date du 26 octobre 2026.

A self-built HTML card headed 'Supply chain, 23 September 2026, all times UTC', titled 'Four and a half hours on npm and PyPI', subtitled 'Each malicious release was the registry's newest version while it was live'. Nine rows, each with a time, an event, a detail and a status chip: 00:48 attacker branch first pushed, pipeline; 02:23 npm 0.1.21 published, 43.6 MB unpacked up from 272 KB, malicious; 03:17 MemOS commit b52958f, six sckit binaries and a CI token stealer, malicious; 03:45 npm 0.1.22 published, matches 0.1.20 apart from version strings, clean; 03:49 npm 0.1.23 published, payload restored three minutes later, malicious; 04:17 GitHub issue 173 filed, disclosure; 04:33 npm 0.1.24 published, clean again and the latest tag for three minutes; 04:36 npm 0.1.25 published, payload back and this one takes the latest tag, malicious; 05:25 PyPI MemoryOS 2.0.34, 19.2 MB wheel against 951 KB in 2.0.33, malicious. A footer reads 'Timestamps from Socket and SafeDep, which agree to the minute. Sizes from Socket.' The OrcaRouter logo is composited in the bottom-right corner.

Que fait sckit une fois le paquet chargé ?

Aucun des deux paquets n’utilisait de hook d’installation, ce qui explique pourquoi le conseil habituel concernant --ignore-scripts ne s’appliquait pas. L’analyse de StepSecurity de la variante npm décrit un nouveau fichier, lib/sckit.js, exportant un lanceur que le code existant de démarrage et de rappel de mémoire du plugin importe et appelle. Au démarrage, il lance un processus détaché .sckit/<os>-<arch>/sckit avec stage0 --config64 et l’environnement complet du parent ; lors du rappel, il fait de même et transmet l’invite utilisateur épurée via une variable d’environnement nommée SCKIT_EVENT_TEXT. Socket a formulé la conséquence sans détour : le binaire s’exécute donc à nouveau à chaque rappel.

La variante PyPI est plus courte et, en pratique, plus difficile à éviter. Socket a découvert que memos/log.py appelle une fonction de déclenchement à l'intérieur de memos/_stage0.py lorsque la journalisation est configurée, que 149 modules appellent le logger au niveau du module et qu'une simple importation du module memos suffit donc à démarrer la charge utile une fois par processus. L'analyse de StepSecurity du même paquet était purement statique — elle ne l'a ni installé, ni importé, ni compilé, ni exécuté.

Le périmètre de la collecte est l’intégralité du répertoire personnel. SafeDep indique la racine de l’inventaire comme $HOME et les fichiers ciblés comme .npmrc, .pypirc, .git-credentials, .netrc, id_rsa, id_ecdsa, id_ed25519, .vault-token, msal_token_cache, access_tokens.json et access_tokens.db, ainsi que les variables d’environnement dont le nom contient token, secret, password, API key ou database URL, ainsi que les formats de jetons reconnus pour GitHub, GitLab, npm, PyPI, Hugging Face, Slack, Stripe, AWS et SendGrid. Les enregistrements OSV des deux paquets, MAL-2026-16476 pour npm et MAL-2026-16475 pour PyPI, décrivent la même liste de fichiers et ajoutent que le protocole de commande et contrôle utilise CBOR, l’échange de clés X25519 et XChaCha20-Poly1305, avec des baux signés et des modules livrés à l’exécution — ainsi, le contenu des modules n’a jamais été observé, seulement le chargeur qui les récupère.

Deux détails déterminent ce qu'un défenseur peut encore récupérer. Le binaire contient des routines d'auto-suppression nommées scheduleSelfDelete et deleteExecutable, selon l'enregistrement OSV, ce qui explique pourquoi la recommandation est de rechercher des artefacts de processus et de réseau plutôt que des fichiers. Et la version 0.1.25 a ajouté lib/tls-trust.js, qui définit SSL_CERT_FILE sur un fichier groupé de 145 certificats d'autorité de certification sur les systèmes Linux minimaux, selon StepSecurity — un correctif visant à faire fonctionner son propre trafic sur des conteneurs dépourvus de magasin de confiance système.

A generated abstract illustration on a dark navy field: at left, a dim cyan isometric tree of folders, several leaves carrying small grey key and badge silhouettes; from those keys a cluster of amber threads lifts away, braids into one bright cord and runs right through a narrow vertical cyan gateway frame that carries an amber keyhole, toward three small red-orange receivers with faint pulse rings on the far right. No text, people or organisation logos appear. The OrcaRouter logo is composited in the bottom-right corner.

Le pipeline de publication était la porte d’entrée.

La reconstitution de SafeDep est le compte rendu le plus détaillé de l'accès initial, et elle pointe vers GitHub Actions plutôt que vers un mot de passe de registre volé. Entre 00:48 et 02:03 UTC, un compte GitHub opérant sous Memtensor-AI a créé, poussé et supprimé une branche nommée sc/release-0.1.21-20260922-cloud cinq fois. Le commit 9b97ec6 a modifié le script de workflow .github/scripts/validate-release-confirmation.mjs afin qu'il ajoute une valeur BASH_ENV à $GITHUB_ENV, ce qui a amené les étapes de shell non interactives ultérieures à charger un fichier fourni par l'attaquant, sckit-publish-bridge.sh, avant d'exécuter leurs propres commandes. Ce pont vérifiait le nom du paquet et le délai d'expiration de 150 secondes de l'étape de publication, appelait le collecteur de jetons avec NPM_TOKEN ou NODE_AUTH_TOKEN, se supprimait et se terminait avec le code 1 — la publication a donc échoué après que le jeton avait été récupéré. La première publication npm malveillante est apparue vingt minutes après la dernière poussée de branche.

La voie PyPI a utilisé le même stratagème contre un pipeline différent. SafeDep rapporte que l’étiquette v2.0.34 pointait vers des commits non signés qui ne se trouvaient pas sur main, et que le commit b52958f, attribué à « MemTensor CI Review », a ajouté les six binaires, un chargeur Python et un backend de compilation personnalisé appelé sckit_poetry_build. Ce backend a écrit un BASH_ENV pointant vers _pypi_bridge.sh dans $GITHUB_ENV ; le pont n’agissait qu’à l’intérieur du wrapper d’envoi twine, récupérait un index signé depuis l’hôte skyleen[.]fr, vérifiait un émetteur avec une implémentation Ed25519, imprimait une ligne de marqueur, désactivait le mot de passe d’envoi et quittait sans rien envoyer. Environ deux heures plus tard, le commit 41bf5c7 a supprimé cet appel avec le message « chore: allow native PyPI upload [skip ci] », l’étiquette a été supprimée et recréée en l’espace de dix secondes, et l’envoi vers PyPI a suivi 62 secondes après. SafeDep interprète le premier commit comme une exécution de capture de jeton et le second comme l’exécution qui a permis au workflow propre à MemTensor de compiler et de publier le paquet malveillant avec le véritable jeton du projet.

Tous les récits ne concordent pas sur ce point. Socket a observé que les versions npm provenaient du même compte npm que les versions légitimes antérieures et ne comportaient pas de gitHead, ce qu’il a interprété comme le signe qu’elles n’avaient pas été produites par la CI du projet, et a déclaré qu’il ne pouvait pas confirmer comment l’attaquant avait obtenu l’accès à la publication. SC Media, qui rapportait le 24 septembre les conclusions de Socket, a tiré la même conclusion d’une compromission de compte. Les deux versions s’accordent sur le fait que les identifiants de publication ont été extraits du pipeline de publication et divergent sur la manière dont l’artefact npm lui-même a été produit ; personne n’a publié les journaux de workflow, car l’API GitHub Actions n’a montré aucune exécution après le 7 septembre et SafeDep déduit que les enregistrements ont été supprimés. La manière dont le compte Memtensor-AI a obtenu l’accès en écriture au départ est inconnue dans les deux récits ; SafeDep qualifie de probable mais non confirmé le vol d’un jeton de compte.

Établi, inféré et absent

La note de confiance du dossier est A et elle n’est pas contestée, ce qu’il vaut la peine de préciser avec exactitude, car la certitude s’attache à ce qui a été publié et non à ce qu’il a fait.

• Établi : le contenu des quatre artefacts, reconstitué par analyse statique chez Socket, StepSecurity et SafeDep ; les heures de publication npm et le dernier tag sur 0.1.25 ; le bond de taille dans les deux écosystèmes ; les commits, les tags et l’historique des branches dans les deux dépôts ; l’infrastructure skyleen[.]fr et les identifiants de campagne cloud-openclaw-semi-nuclear et memos-semi-nuclear ; et la configuration de la charge utile, qui expire les 22–23 octobre 2026 selon Socket et SafeDep.

• Inféré : que l’outil a été écrit avec l’aide d’un modèle de langage, ce que SafeDep affirme directement et qualifie d’inférence ; et que les jetons de publication volés étaient destinés à être réutilisés, ce que la chaîne de canal de la configuration — « exact-ref-one-use-NPM_TOKEN » — étaye sans le prouver.

• Non établi : qui était l'attaquant ; si une copie de sckit s'est un jour exécutée en dehors d'un bac à sable ; si un identifiant a quitté un hôte ; si une invite saisie dans un agent OpenClaw a atteint l'attaquant ; et si un paquet en aval a été republié. Socket et StepSecurity ont tous deux travaillé de manière statique, et StepSecurity déclare qu'aucune exécution de processus, aucune transmission de secret, aucune exfiltration réussie, aucune installation de workflow et aucune publication supplémentaire de paquet n'a été observée dans son analyse.

Cet écart explique pourquoi l'archive consigne cet incident avec les dommages réels marqués comme faux. L'exposition est réelle et étendue — toute personne ayant installé une version affectée est invitée à considérer l'hôte comme compromis — mais aucun des quatre cabinets d'investigation n'a consigné de dommages, au sens d'un identifiant dont l'utilisation est démontrée ou d'un système dont l'accès est démontré.

A self-built HTML card headed 'What the record supports', titled 'Four investigating firms, no confirmed victim', subtitled 'Socket, StepSecurity, SafeDep and Aikido documented the same four artefacts on 23 September 2026'. The left column, headed 'Established by static analysis', lists: four malicious releases, npm 0.1.21, 0.1.23 and 0.1.25 and PyPI MemoryOS 2.0.34; one implant, the same stripped Go binary sckit in all four; the publish window 02:23 to 05:25 UTC with 0.1.25 tagged latest; package growth from 272 KB to 43.6 MB on npm and 951 KB to 19.2 MB for the PyPI wheel; six builds per package for Linux, macOS and Windows on amd64 and arm64; collection scoped to the whole home directory across registry, source-control, cloud, Vault and SSH secrets; exfiltration to servers under skyleen[.]fr; and OSV tracking as MAL-2026-16476 and MAL-2026-16475. The right column, headed 'Not established', lists: the attacker's identity; how Memtensor-AI obtained push access; whether any copy ever executed outside static analysis; whether a stolen secret reached the attacker; whether any prompt text was received; and whether any downstream package was republished. A footer reads 'Archive classification for this record: incident, severity high, confidence A, real harm false.' The OrcaRouter logo is composited in the bottom-right corner.

Le ver que l’on n’a jamais vu se propager

Les binaires contiennent plus qu’un stealer. SafeDep répertorie des fonctions nommées recursivePublish, prepareRemoteNode, prepareRemotePython et prepareRemoteWorkflow, ainsi qu’un modèle GitHub Actions intégré déclenché sur push et la capacité d’écrire un fichier de workflow nommé runtime-update.yml dans les dépôts que l’implant peut atteindre. Socket a trouvé des chaînes concernant l’encodage des manifestes de paquets et l’installation de fichiers de dépôt, et a estimé que les binaires pourraient être capables de republier des paquets à l’aide de jetons de registre volés.

Il s'agit de la conception d'un ver. Ce n'est pas un ver dont le fonctionnement a été observé. StepSecurity n'a trouvé aucune preuve que cette logique ait jamais servi à se propager, et la note de recherche du Cloud Security Alliance du 25 septembre interprète cette capacité comme un potentiel d'auto-propagation limité plutôt que comme un comportement de ver confirmé. Plusieurs médias ont décrit ce cas comme le premier ver de la chaîne d'approvisionnement visant l'infrastructure de mémoire des agents d'IA ; cette description est juste en tant qu'affirmation sur la conception, mais les rapports ne l'étayent pas en tant qu'affirmation sur ce qui s'est produit.

Qu’est-ce qui a changé par la suite ?

Les registres ont été nettoyés le jour même. Lors d'une vérification le 26 octobre 2026, les métadonnées du paquet npm ne listent plus 0.1.21, 0.1.23 ni 0.1.25 parmi ses versions, l'étiquette latest pointe vers 0.1.24, et le paquet a été modifié pour la dernière fois à 11:53 UTC le 23 septembre. Deux dist-tags inhabituels sont encore attachés — clean-inverse-0-1-23 pointant vers 0.1.22 et clean-inverse-0-1-25 pointant vers 0.1.24 — reflétant les deux versions propres qui ont brièvement interrompu les versions malveillantes. Sur PyPI, MemoryOS 2.0.34 a disparu et la version la plus récente est 2.0.33, mise en ligne le 3 septembre 2026 ; aucune autre publication PyPI n'a eu lieu depuis sept semaines.

Ce qui n’a pas changé, c’est la communication publique du projet. Le dépôt ne répertorie aucun avis de sécurité, le ticket GitHub de 04:17 le jour de l’incident est toujours ouvert et sans réponse, et aucun des reportages examinés ici ne cite de déclaration de MemTensor ni d’OpenClaw. Aucun des deux paquets malveillants ne portait d’attestation de provenance, ce qui, selon SafeDep, supprimait le seul signal indiquant que les artefacts avaient été construits à partir d’un commit inattendu.

La réponse qui existe bel et bien se situe du côté de la détection : StepSecurity a ajouté les domaines de commande et contrôle skyleen[.]fr à sa liste de blocage globale, et les deux paquets sont répertoriés dans OSV sous MAL-2026-16476 et MAL-2026-16475.

Si une version affectée a été installée

Les recommandations de Socket sont les plus strictes et les plus simples : si l’une des versions listées a été chargée ou importée, considérez cet hôte comme compromis, y compris les runners CI et les conteneurs qui n’ont exécuté que des tests. Les mesures concrètes sur lesquelles les quatre entreprises s’accordent :

• Supprimez le paquet et épinglez-le sur une version propre — npm 0.1.20, PyPI 2.0.33. StepSecurity prévient que son analyse n’a pas permis d’établir une base de référence PyPI propre, tandis que SafeDep et SC Media désignent tous deux la version 2.0.33 comme la dernière version fiable.

• Recherchez la ligne de commande du processus, car l’exécutable peut se supprimer lui-même : sckit stage0 --config64, ainsi que la variable d’environnement SCKIT_EVENT_TEXT.

• Supprimez les répertoires d'état ~/.openclaw/.cache/runtime et ~/.memos/.cache/runtime, et vérifiez dans les dépôts la présence inattendue d'un workflow runtime-update.yml ou de répertoires .sckit/.

• Renouvelez chaque identifiant accessible depuis le répertoire personnel et l’environnement du processus, depuis une machine propre : npm, PyPI, GitHub, GitLab, AWS, Vault, SSH, Hugging Face, Slack, Stripe et SendGrid, ainsi que toutes les valeurs .env. Les jetons de registre et de gestion de code source passent en premier.

• Passez en revue les versions publiées de vos propres paquets, au cas où un jeton de publication volé aurait servi à livrer des versions que vous n'avez pas écrites.

• Bloquer skyleen[.]fr et ses sous-domaines, et consulter les journaux de sortie jusqu’au 23 septembre.

• Considérez comme exposés toute invite ou tout secret collé ayant transité par le plugin pendant qu’il était actif. C’est la partie de l’incident propre à la mémoire de l’agent : l’implant n’avait pas besoin d’extraire une transcription, car la transcription lui était fournie à chaque rappel.