Skip to main content

Por dentro do comprometimento do keyv npm: malware no preinstall, procedência confiável e hooks de IDE

feature insights context

4 de agosto de 2026

0 minutos de leitura

Em 4 de agosto de 2026, invasores comprometeram o processo de publicação do keyv e de pacotes npm relacionados. As versões maliciosas adicionam um hook preinstall que executa um carregador ofuscado antes do início do código da aplicação e, em seguida, inicia um payload de segundo estágio muito maior.

Este é um incidente ativo na cadeia de suprimentos de software, não uma prova de conceito. A Snyk Security Research baixou e comparou de forma independente os tarballs publicados, sem instalá-los, listou todos os pacotes retornados pelo npm para o mantenedor jaredwray e identificou 11 versões maliciosas com os mesmos dois arquivos de payload. Às 11:16 UTC, oito dessas versões ainda estavam marcadas como latest.

Em resumo

  • Incidente: Código malicioso embutido em pacotes npm legítimos

  • Pacote principal: keyv@6.0.0

  • Versões afetadas identificadas pela Snyk: 11 em pacotes relacionados a keyv, cacheable e ecto

  • Execução: "preinstall": "node setup.mjs"

  • Payload: setup.mjs carrega um segundo estágio de 727.680 bytes chamado Math_Symbol.js

  • Status observado às 11:16 UTC: Oito versões maliciosas continuavam com a tag latest. Três haviam sido removidas.

  • Comunicado: SNYK-JS-KEYV-18515941

  • CVE/GHSA: Nenhum CVE ou GHSA havia sido atribuído durante a pesquisa

  • Severidade operacional: Crítica, pois a instalação permite a execução de código controlado pelo invasor com os privilégios do desenvolvedor ou do executor de CI

  • Correção: Fixe a versão ou reverta para uma versão considerada segura. Nenhuma versão posterior segura ao keyv@6.0.0 havia sido publicada.

  • Ação imediata: Não instale as versões afetadas. Se alguma delas foi executada, isole o host, procure e desative mecanismos de persistência e, usando um sistema limpo, troque as credenciais expostas.

Severidade e status do comunicado

A Snyk publicou SNYK-JS-KEYV-18515941 enquanto este rascunho estava sendo preparado. O comunicado classifica o keyv@6.0.0 como código malicioso embutido, de acordo com a CWE-506.

Durante nossa pesquisa, nenhum CVE ou registro de comunicado do GitHub havia sido atribuído. A instalação afetada executa código controlado pelo invasor sem exigir privilégios adicionais nem interação com a aplicação, usando as permissões e credenciais disponíveis ao desenvolvedor ou ao processo de CI.

O que a Snyk confirmou de forma independente

Consultamos o endpoint de busca do registro npm para pacotes mantidos por jaredwray. O endpoint retornou 61 nomes de pacotes. Para cada nome, obtivemos o manifesto do registro, examinamos todas as versões publicadas em 4 de agosto e verificamos os scripts de ciclo de vida e os metadados dos tarballs.

A varredura identificou as versões maliciosas abaixo e confirmou que os outros pacotes do escopo @keyv não foram comprometidos:

A última entrada é importante para delimitar o incidente. O ecto@5.0.1 apareceu depois dos primeiros alertas públicos, e os arquivos setup.mjs e Math_Symbol.js são idênticos byte a byte aos arquivos do keyv@6.0.0. Listas iniciais de pacotes que não incluem o ecto estão incompletas.

No nosso registro às 11:16 UTC, o npm havia removido flat-cache@6.1.24, cacheable-request@13.0.20 e cache-manager@7.2.10, revertendo suas tags latest para 6.1.23, 13.0.19 e 7.2.9. As outras oito versões afetadas ainda estavam disponíveis e marcadas como latest. O estado do registro pode mudar rapidamente durante um incidente ativo. Por isso, espelhos privados e arquivos de lock continuam fazendo parte da investigação mesmo depois que o npm remove uma versão.

O tarball publicado difere em três pontos

Baixamos o keyv@6.0.0 e seu release candidate, keyv@6.0.0-rc.1, diretamente do registro e comparamos todos os arquivos usando SHA-256. Não instalamos os pacotes nem executamos nenhum dos payloads.

Apenas três caminhos são diferentes:

  • O package.json mudou da versão 6.0.0-rc.1 para 6.0.0, adicionou os dois arquivos de payload à lista de arquivos publicados e incluiu o script preinstall.

  • O setup.mjs foi adicionado com 29.918 bytes.

  • O Math_Symbol.js foi adicionado com 727.680 bytes.

Todos os arquivos em dist/ são idênticos byte a byte entre o release candidate e a versão estável comprometida. A biblioteca continua funcionando normalmente após a instalação, enquanto o hook de ciclo de vida é executado separadamente. Essa pequena diferença é uma pista útil para detecção e uma técnica eficaz de ocultação.

A alteração no manifesto é direta:

 "scripts": {
   "build": "tsdown",
+  "preinstall": "node setup.mjs"
 },
 "files": [
   "dist",
   "LICENSE",
+  "setup.mjs",
+  "Math_Symbol.js"
 ]

O manifesto do keyv@6.0.0 no registro informa o seguinte valor de integridade do tarball:

sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==

Nossos hashes independentes são:

keyv-6.0.0.tgz
sha256 d584f9b6af48b7ed1f93713944f033783bf149e1c25e1643eb8c0e9df5dc7782

setup.mjs, 29,918 bytes
sha256 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668

Math_Symbol.js, 727,680 bytes
sha256 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc

Em seguida, repetimos a verificação dos hashes dos payloads em todas as versões afetadas que podiam ser obtidas durante nossa análise inicial. Todos os nove tarballs disponíveis naquele momento continham o mesmo setup.mjs de 29.918 bytes e o mesmo Math_Symbol.js de 727.680 bytes, com exatamente os hashes acima. Mais tarde, o npm removeu uma dessas versões. Para detectar o problema, use hashes e o comportamento dos scripts de ciclo de vida, em vez de se basear em um único nome de pacote.

Como o malware é executado

O npm executa automaticamente o preinstall durante a instalação de dependências. O desenvolvedor não precisa importar o keyv, iniciar uma aplicação nem chamar uma API vulnerável. Resolver e instalar o pacote afetado é suficiente.

A análise estática do setup.mjs mostra verificações de plataforma para Linux, macOS e Windows, uso de child_process.execFileSync, operações no sistema de arquivos, acesso HTTPS e tratamento do runtime Bun. O carregador menos ofuscado, armazenado em .claude/setup.mjs, identifica a versão 1.3.13 do Bun e o nome do arquivo de segundo estágio, math_init.js. Se o Bun não estiver instalado, o carregador pode baixar uma versão compatível do Bun pelo GitHub, executar o payload maior e remover os arquivos temporários do runtime.

Uma análise independente do malware, fornecida junto com os relatórios do incidente, indica que o segundo estágio criptografado tem como alvo tokens do GitHub e do npm, credenciais de nuvem, chaves privadas, strings de conexão com bancos de dados, tokens do Vault, tokens de contas de serviço do Kubernetes e a memória dos executores do GitHub Actions. O relatório também descreve um mecanismo de persistência gh-token-monitor, que monitora um token do GitHub roubado e executa um handler fornecido quando o token deixa de funcionar.

Não executamos nem descriptografamos de forma independente o Math_Symbol.js. Por isso, esses detalhes sobre as capacidades do segundo estágio devem manter essa atribuição. As orientações operacionais são compatíveis com a análise anterior da Snyk sobre o mesmo padrão de persistência gh-token-monitor: contenha e desative o monitor antes de revogar os tokens.

Um segundo caminho de execução tem como alvo ferramentas de desenvolvimento

O hook de ciclo de vida do npm é apenas um dos pontos de entrada. Um registro da API do GitHub para o commit d8c850c7 mostra cinco arquivos adicionados ao repositório keyv:

  • .claude/settings.json

  • .claude/setup.mjs

  • .claude/math_init.js

  • .vscode/tasks.json

  • .vscode/setup.mjs

A configuração do Claude registra um comando SessionStart que invoca .vscode/setup.mjs. A tarefa do VS Code usa runOn: "folderOpen" e invoca .claude/setup.mjs. Esses arquivos criam um caminho de execução adicional quando um desenvolvedor ou agente de programação confia na configuração local do projeto e a ativa. Dependendo da confiança no workspace e das configurações do usuário, o VS Code pode pedir autorização antes de permitir tarefas automáticas. Portanto, abrir um checkout é uma condição de exposição, não uma prova de que o código foi executado.

O GitHub verificou criptograficamente o commit, que usa github-actions[bot] como identidade do autor. O selo de verificação comprova que o GitHub assinou o objeto do commit. Ele não comprova que a alteração foi autorizada pelo mantenedor do projeto. As evidências são compatíveis com o comprometimento de uma conta, credencial, sessão ou processo de publicação. Elas não identificam quem estava por trás da operação, e o mantenedor deve ser tratado como vítima do incidente.

A versão maliciosa foi publicada com procedência válida

O manifesto do npm identifica o GitHub Actions como publicador confiável do keyv@6.0.0 e inclui um link para um atestado npm da versão. O código-fonte malicioso estava presente no estado marcado do repositório, então o fluxo de trabalho legítimo compilou e atestou o artefato malicioso.

O patch do commit de lançamento também adicionou um teste que executava o setup.mjs por meio de execFileSync. Um commit posterior removeu apenas esse teste. Portanto, a execução da suíte de testes da versão poderia ter executado o malware na CI antes da publicação.

A procedência continua sendo uma evidência valiosa da origem da compilação. Este incidente mostra seu limite: ela pode atestar fielmente uma compilação cujo código-fonte ou contexto do fluxo de trabalho já foi comprometido.

Impacto e provável alcance

Os principais pacotes afetados têm volumes de instalação muito altos. De 5 de julho a 3 de agosto, o npm registrou 619.682.667 downloads do keyv, 579.751.309 do flat-cache e 571.240.025 do file-entry-cache.

Esses números têm grande sobreposição, pois os pacotes dependem uns dos outros e aparecem nas mesmas cadeias de ferramentas. Eles medem o alcance no ecossistema, não o número de hosts comprometidos. Além disso, o período de exposição de cada versão maliciosa foi bem menor que um mês.

A Snyk observa um amplo alcance potencial entre os projetos monitorados. O uso transitivo importa, pois flat-cache e file-entry-cache costumam ser incluídos por ferramentas de desenvolvimento, como o ESLint. Laptops de desenvolvedores e executores de CI são alvos especialmente valiosos porque muitas vezes armazenam credenciais do GitHub, npm, nuvem, assinatura de código e implantação.

Até o momento da publicação, não há uma contagem pública verificada de execuções bem-sucedidas do segundo estágio ou de credenciais exfiltradas. Os pacotes foram publicados ativamente no registro de produção do npm e, no nosso registro, oito ainda estavam marcados como latest. Isso confirma que houve distribuição real, não apenas uma exploração em laboratório. O comunicado da Snyk classifica a maturidade da exploração como “Atacada”, mas não divulga o número de vítimas.

Como detectar a exposição

Primeiro, inspecione a árvore de dependências resolvida, incluindo as dependências transitivas:

npm ls keyv flat-cache file-entry-cache cacheable-request cacheable \
  @cacheable/utils cache-manager @cacheable/net \
  @cacheable/node-cache @cacheable/memory ecto --all

Pesquise nos arquivos de lock, pois uma versão afetada pode continuar fixada mesmo depois que o npm altera uma dist-tag:

rg -n \
  'keyv|flat-cache|file-entry-cache|cacheable-request|cacheable|cache-manager|@cacheable/|ecto' \
  package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock

Inspecione os manifestos instalados em busca do hook exato, sem executar o código do pacote:

find node_modules -name package.json -print0 |
  xargs -0 node -e '
    const fs = require("node:fs");
    for (const file of process.argv.slice(1)) {
      try {
        const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
        if (pkg.scripts?.preinstall === "node setup.mjs") {
          console.log(`${pkg.name}@${pkg.version} ${file}`);
        }
      } catch {}
    }
  '

Procure os indicadores do payload e de persistência:

find "$HOME" /tmp \
  \( -name setup.mjs -o -name Math_Symbol.js -o -name math_init.js \
     -o -name gh-token-monitor.sh -o -name gh-token-monitor.service \
     -o -name com.user.gh-token-monitor.plist \) \
  -print 2>/dev/null

Inspecione também os repositórios acessíveis com credenciais expostas do GitHub em busca de arquivos .claude/settings.json ou .vscode/tasks.json inesperados, arquivos de workflow com toJSON(secrets) e artefatos do GitHub Actions recém-criados.

Clientes da Snyk podem consultar o novo comunicado sobre keyv@6.0.0, executar novamente os testes de dependências e monitorar os projetos conforme novas informações sobre o incidente forem divulgadas:

snyk test --all-projects
snyk monitor --all-projects

A lição do Snyk Learn sobre pacotes legítimos comprometidos oferece mais contexto sobre como integrar verificações de integridade de dependências e controles do registro aos fluxos de trabalho de desenvolvimento habituais.

Correção

Se um pacote afetado foi instalado

Trate a máquina ou o executor como potencialmente comprometido, mesmo que o node_modules já tenha sido excluído.

  1. Isole o host do acesso normal à rede. Preserve logs, dados de processos, logs do npm, saída dos jobs de CI e registros de data e hora do sistema de arquivos para a investigação.

  2. Investigue a persistência de gh-token-monitor antes de revogar as credenciais do GitHub. Verifique ~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/, ~/.config/systemd/user/gh-token-monitor.service e ~/Library/LaunchAgents/com.user.gh-token-monitor.plist.

  3. Desative qualquer persistência encontrada. Coordene a remoção com a equipe de resposta a incidentes e preserve uma cópia forense. A revogação pode ser detectada pelo monitor.

  4. Troque as credenciais em um sistema reconhecidamente limpo. Inclua tokens PAT e tokens de aplicativos do GitHub, tokens do npm, credenciais da AWS, do GCP, do Azure, do Vault e do Kubernetes, credenciais de bancos de dados, chaves privadas e todos os segredos acessíveis aos jobs de CI afetados.

  5. Revise os logs de auditoria das contas e da nuvem. Procure publicações inesperadas no npm, criação de repositórios, alterações em workflows, artefatos do Actions, chamadas de API na nuvem e uso de tokens em locais desconhecidos.

  6. Remova os artefatos afetados de registros privados e caches. A remoção do npm não apaga cópias já armazenadas em um proxy interno ou no cache de um desenvolvedor.

Fixe versões reconhecidamente limpas antes de reinstalar

As versões anteriores abaixo não tinham um hook de ciclo de vida de instalação nos manifestos do registro quando as verificamos:

{
  "overrides": {
    "keyv": "5.6.0",
    "flat-cache": "6.1.23",
    "file-entry-cache": "11.1.5",
    "cacheable-request": "13.0.19",
    "cacheable": "2.5.0",
    "@cacheable/utils": "2.5.0",
    "cache-manager": "7.2.9",
    "@cacheable/net": "2.1.0",
    "@cacheable/node-cache": "3.1.1",
    "@cacheable/memory": "2.2.0",
    "ecto": "5.0.0"
  }
}

keyv@5.6.0 é a versão estável 5.x mais recente que consideramos limpa na nossa captura. Voltar de keyv@6.0.0 para a versão 5.x pode exigir alterações no código. 6.0.0-rc.1 tinha conteúdo dist/ idêntico byte a byte e nenhum hook de ciclo de vida, mas equipes de produção devem preferir uma versão estável já consolidada, a menos que tenham validado explicitamente a versão candidata.

Depois de adicionar as substituições, gere novamente o arquivo de lock sem executar scripts de ciclo de vida:

npm install --package-lock-only --ignore-scripts
rm -rf node_modules
npm cache clean --force
npm ci --ignore-scripts
npm ls keyv flat-cache file-entry-cache cacheable-request cacheable \
  @cacheable/utils cache-manager @cacheable/net \
  @cacheable/node-cache @cacheable/memory ecto --all

Desativar os scripts de ciclo de vida limita esse caminho de execução. Alguns pacotes legítimos precisam de scripts de instalação; por isso, as equipes devem manter uma lista de permissões restrita, em vez de habilitar scripts globalmente. As práticas recomendadas de segurança para npm da Snyk explicam em mais detalhes as instalações determinísticas, o controle de scripts e a análise de pacotes.

Linha do tempo do incidente

Todos os horários abaixo estão em UTC, no dia 4 de agosto de 2026.

  • 09:02 a 09:17: O commit ee2681a9 prepara keyv@6.0.0, adiciona o hook de ciclo de vida, os arquivos do payload e um teste que executa o loader. O GitHub registra o horário de autoria como 09:02 e o horário do commit como 09:17.

  • 09:04: O commit verificado d8c850c7 adiciona os hooks de execução do Claude e do VS Code.

  • 09:23: O commit f97eabcd remove o teste de preinstall.

  • 09:30 a 09:32: Vários pacotes @keyv/* na versão 6 são publicados sem o hook malicioso.

  • 09:35: O npm publica keyv@6.0.0 com o hook malicioso.

  • 09:51: O commit 1f79edd8 adiciona os arquivos do payload aos workspaces de @keyv/*, criando riscos para versões posteriores.

  • 09:49 a 09:51: As issues #2044, #2045 e #2046 do GitHub relatam o incidente. Mais tarde, a API de issues retornou 410 Gone.

  • 10:09 a 10:14: As versões maliciosas da família Cacheable são publicadas.

  • 10:18 e 10:20: O pesquisador de segurança Charlie Eriksen publica os dois alertas públicos fornecidos.

  • 10:28: ecto@5.0.1 é publicado com o mesmo payload, ampliando para 11 a lista de pacotes vinculados ao mantenedor.

  • 10:39: Os metadados do registro npm mudam após a remoção de cacheable-request@13.0.20.

  • 10:42: Os metadados do registro npm mudam após a remoção de flat-cache@6.1.24.

  • 11:11: Os metadados do registro npm mudam após a remoção de cache-manager@7.2.10.

  • 11:16: A captura do registro da Snyk ainda encontra oito versões maliciosas na tag latest.

  • Durante a elaboração deste texto: A Snyk publica SNYK-JS-KEYV-18515941 e cerca de 70 outros avisos.

Esta linha do tempo reflete um incidente em andamento. Confira novamente os manifestos do npm, as dist-tags e os avisos da Snyk imediatamente antes da publicação.

Webinar sob demanda

A OpenAI corrigiu a própria prova e depois invadiu o ambiente de produção

Assista à gravação sob demanda para entender por que a autovalidação falha por princípio, como uma arquitetura com vários modelos agrava o problema e como funciona a validação independente na prática. Conheça uma estrutura para governar todos os ativos de IA do seu ambiente, independentemente do laboratório que os desenvolveu.